From 4be32f896a7fc7e9db8b92132b147870bd57bc9b Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Fri, 8 Oct 2021 15:09:20 -0700 Subject: [PATCH 01/35] Add test case for #57478 --- src/test/ui/generator/issue-57478.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/test/ui/generator/issue-57478.rs diff --git a/src/test/ui/generator/issue-57478.rs b/src/test/ui/generator/issue-57478.rs new file mode 100644 index 0000000000000..592632cd35102 --- /dev/null +++ b/src/test/ui/generator/issue-57478.rs @@ -0,0 +1,14 @@ +#![feature(negative_impls, generators)] + +struct Foo; +impl !Send for Foo {} + +fn main() { + assert_send(|| { + let guard = Foo; + drop(guard); + yield; + }) +} + +fn assert_send(_: T) {} From f712df8c5dfa14a01525dc28f38d731eedcc7263 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Wed, 20 Oct 2021 16:42:53 -0700 Subject: [PATCH 02/35] Track drop points in generator_interior This change adds the basic infrastructure for tracking drop ranges in generator interior analysis, which allows us to exclude dropped types from the generator type. Not yet complete, but many of the async/await and generator tests pass. The main missing piece is tracking branching control flow (e.g. around an `if` expression). The patch does include support, however, for multiple yields in th e same block. Issue #57478 --- compiler/rustc_passes/src/region.rs | 1 - .../src/check/generator_interior.rs | 190 +++++++++++++++--- src/test/ui/async-await/async-fn-nonsend.rs | 8 +- .../ui/async-await/unresolved_type_param.rs | 16 -- .../async-await/unresolved_type_param.stderr | 50 +---- src/test/ui/generator/issue-57478.rs | 2 + 6 files changed, 168 insertions(+), 99 deletions(-) diff --git a/compiler/rustc_passes/src/region.rs b/compiler/rustc_passes/src/region.rs index db699a56645c2..f8989200d7e08 100644 --- a/compiler/rustc_passes/src/region.rs +++ b/compiler/rustc_passes/src/region.rs @@ -255,7 +255,6 @@ fn resolve_expr<'tcx>(visitor: &mut RegionResolutionVisitor<'tcx>, expr: &'tcx h hir::ExprKind::AssignOp(..) | hir::ExprKind::Index(..) | hir::ExprKind::Unary(..) - | hir::ExprKind::Call(..) | hir::ExprKind::MethodCall(..) => { // FIXME(https://github.com/rust-lang/rfcs/issues/811) Nested method calls // diff --git a/compiler/rustc_typeck/src/check/generator_interior.rs b/compiler/rustc_typeck/src/check/generator_interior.rs index fb6e11dbfb738..a406a1a8ecd99 100644 --- a/compiler/rustc_typeck/src/check/generator_interior.rs +++ b/compiler/rustc_typeck/src/check/generator_interior.rs @@ -3,7 +3,10 @@ //! is calculated in `rustc_const_eval::transform::generator` and may be a subset of the //! types computed here. +use crate::expr_use_visitor::{self, ExprUseVisitor}; + use super::FnCtxt; +use hir::HirIdMap; use rustc_data_structures::fx::{FxHashSet, FxIndexSet}; use rustc_errors::pluralize; use rustc_hir as hir; @@ -34,6 +37,7 @@ struct InteriorVisitor<'a, 'tcx> { guard_bindings: SmallVec<[SmallVec<[HirId; 4]>; 1]>, guard_bindings_set: HirIdSet, linted_values: HirIdSet, + drop_ranges: HirIdMap, } impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> { @@ -48,9 +52,11 @@ impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> { ) { use rustc_span::DUMMY_SP; + let ty = self.fcx.resolve_vars_if_possible(ty); + debug!( - "generator_interior: attempting to record type {:?} {:?} {:?} {:?}", - ty, scope, expr, source_span + "attempting to record type ty={:?}; hir_id={:?}; scope={:?}; expr={:?}; source_span={:?}; expr_count={:?}", + ty, hir_id, scope, expr, source_span, self.expr_count, ); let live_across_yield = scope @@ -68,6 +74,14 @@ impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> { yield_data.expr_and_pat_count, self.expr_count, source_span ); + match self.drop_ranges.get(&hir_id) { + Some(range) if range.contains(yield_data.expr_and_pat_count) => { + debug!("value is dropped at yield point; not recording"); + return None + } + _ => (), + } + // If it is a borrowing happening in the guard, // it needs to be recorded regardless because they // do live across this yield point. @@ -85,7 +99,6 @@ impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> { }); if let Some(yield_data) = live_across_yield { - let ty = self.fcx.resolve_vars_if_possible(ty); debug!( "type in expr = {:?}, scope = {:?}, type = {:?}, count = {}, yield_span = {:?}", expr, scope, ty, self.expr_count, yield_data.span @@ -154,7 +167,6 @@ impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> { self.expr_count, expr.map(|e| e.span) ); - let ty = self.fcx.resolve_vars_if_possible(ty); if let Some((unresolved_type, unresolved_type_span)) = self.fcx.unresolved_type_vars(&ty) { @@ -166,6 +178,39 @@ impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> { } } } + + fn visit_call( + &mut self, + call_expr: &'tcx Expr<'tcx>, + callee: &'tcx Expr<'tcx>, + args: &'tcx [Expr<'tcx>], + ) { + match &callee.kind { + ExprKind::Path(qpath) => { + let res = self.fcx.typeck_results.borrow().qpath_res(qpath, callee.hir_id); + match res { + // Direct calls never need to keep the callee `ty::FnDef` + // ZST in a temporary, so skip its type, just in case it + // can significantly complicate the generator type. + Res::Def( + DefKind::Fn | DefKind::AssocFn | DefKind::Ctor(_, CtorKind::Fn), + _, + ) => { + // NOTE(eddyb) this assumes a path expression has + // no nested expressions to keep track of. + self.expr_count += 1; + + // Record the rest of the call expression normally. + for arg in args { + self.visit_expr(arg); + } + } + _ => intravisit::walk_expr(self, call_expr), + } + } + _ => intravisit::walk_expr(self, call_expr), + } + } } pub fn resolve_interior<'a, 'tcx>( @@ -176,6 +221,20 @@ pub fn resolve_interior<'a, 'tcx>( kind: hir::GeneratorKind, ) { let body = fcx.tcx.hir().body(body_id); + + let mut drop_range_visitor = DropRangeVisitor::default(); + + // Run ExprUseVisitor to find where values are consumed. + ExprUseVisitor::new( + &mut drop_range_visitor, + &fcx.infcx, + def_id.expect_local(), + fcx.param_env, + &fcx.typeck_results.borrow(), + ) + .consume_body(body); + intravisit::walk_body(&mut drop_range_visitor, body); + let mut visitor = InteriorVisitor { fcx, types: FxIndexSet::default(), @@ -186,6 +245,7 @@ pub fn resolve_interior<'a, 'tcx>( guard_bindings: <_>::default(), guard_bindings_set: <_>::default(), linted_values: <_>::default(), + drop_ranges: drop_range_visitor.drop_ranges, }; intravisit::walk_body(&mut visitor, body); @@ -313,32 +373,9 @@ impl<'a, 'tcx> Visitor<'tcx> for InteriorVisitor<'a, 'tcx> { fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { let mut guard_borrowing_from_pattern = false; + match &expr.kind { - ExprKind::Call(callee, args) => match &callee.kind { - ExprKind::Path(qpath) => { - let res = self.fcx.typeck_results.borrow().qpath_res(qpath, callee.hir_id); - match res { - // Direct calls never need to keep the callee `ty::FnDef` - // ZST in a temporary, so skip its type, just in case it - // can significantly complicate the generator type. - Res::Def( - DefKind::Fn | DefKind::AssocFn | DefKind::Ctor(_, CtorKind::Fn), - _, - ) => { - // NOTE(eddyb) this assumes a path expression has - // no nested expressions to keep track of. - self.expr_count += 1; - - // Record the rest of the call expression normally. - for arg in *args { - self.visit_expr(arg); - } - } - _ => intravisit::walk_expr(self, expr), - } - } - _ => intravisit::walk_expr(self, expr), - }, + ExprKind::Call(callee, args) => self.visit_call(expr, callee, args), ExprKind::Path(qpath) => { intravisit::walk_expr(self, expr); let res = self.fcx.typeck_results.borrow().qpath_res(qpath, expr.hir_id); @@ -617,3 +654,98 @@ fn check_must_not_suspend_def( } false } + +/// This struct facilitates computing the ranges for which a place is uninitialized. +#[derive(Default)] +struct DropRangeVisitor { + consumed_places: HirIdSet, + drop_ranges: HirIdMap, + expr_count: usize, +} + +impl DropRangeVisitor { + fn record_drop(&mut self, hir_id: HirId) { + debug!("marking {:?} as dropped at {}", hir_id, self.expr_count); + self.drop_ranges.insert(hir_id, DropRange { dropped_at: self.expr_count }); + } + + /// ExprUseVisitor's consume callback doesn't go deep enough for our purposes in all + /// expressions. This method consumes a little deeper into the expression when needed. + fn consume_expr(&mut self, expr: &hir::Expr<'_>) { + self.record_drop(expr.hir_id); + match expr.kind { + hir::ExprKind::Path(hir::QPath::Resolved( + _, + hir::Path { res: hir::def::Res::Local(hir_id), .. }, + )) => { + self.record_drop(*hir_id); + } + _ => (), + } + } +} + +impl<'tcx> expr_use_visitor::Delegate<'tcx> for DropRangeVisitor { + fn consume( + &mut self, + place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>, + diag_expr_id: hir::HirId, + ) { + debug!("consume {:?}; diag_expr_id={:?}", place_with_id, diag_expr_id); + self.consumed_places.insert(place_with_id.hir_id); + } + + fn borrow( + &mut self, + _place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>, + _diag_expr_id: hir::HirId, + _bk: rustc_middle::ty::BorrowKind, + ) { + } + + fn mutate( + &mut self, + _assignee_place: &expr_use_visitor::PlaceWithHirId<'tcx>, + _diag_expr_id: hir::HirId, + ) { + } + + fn fake_read( + &mut self, + _place: expr_use_visitor::Place<'tcx>, + _cause: rustc_middle::mir::FakeReadCause, + _diag_expr_id: hir::HirId, + ) { + } +} + +impl<'tcx> Visitor<'tcx> for DropRangeVisitor { + type Map = intravisit::ErasedMap<'tcx>; + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } + + fn visit_expr(&mut self, expr: &Expr<'_>) { + intravisit::walk_expr(self, expr); + + self.expr_count += 1; + + if self.consumed_places.contains(&expr.hir_id) { + self.consume_expr(expr); + } + } +} + +struct DropRange { + /// The post-order id of the point where this expression is dropped. + /// + /// We can consider the value dropped at any post-order id greater than dropped_at. + dropped_at: usize, +} + +impl DropRange { + fn contains(&self, id: usize) -> bool { + id >= self.dropped_at + } +} diff --git a/src/test/ui/async-await/async-fn-nonsend.rs b/src/test/ui/async-await/async-fn-nonsend.rs index 845941200fc95..4dd36e7f0f062 100644 --- a/src/test/ui/async-await/async-fn-nonsend.rs +++ b/src/test/ui/async-await/async-fn-nonsend.rs @@ -18,7 +18,7 @@ async fn fut() {} async fn fut_arg(_: T) {} async fn local_dropped_before_await() { - // FIXME: it'd be nice for this to be allowed in a `Send` `async fn` + // this is okay now because of the drop let x = non_send(); drop(x); fut().await; @@ -36,7 +36,7 @@ async fn non_send_temporary_in_match() { } async fn non_sync_with_method_call() { - // FIXME: it'd be nice for this to work. + let f: &mut std::fmt::Formatter = panic!(); if non_sync().fmt(f).unwrap() == () { fut().await; @@ -47,9 +47,9 @@ fn assert_send(_: impl Send) {} pub fn pass_assert() { assert_send(local_dropped_before_await()); - //~^ ERROR future cannot be sent between threads safely + assert_send(non_send_temporary_in_match()); //~^ ERROR future cannot be sent between threads safely assert_send(non_sync_with_method_call()); - //~^ ERROR future cannot be sent between threads safely + } diff --git a/src/test/ui/async-await/unresolved_type_param.rs b/src/test/ui/async-await/unresolved_type_param.rs index 85d868c27032e..79c043b701ddb 100644 --- a/src/test/ui/async-await/unresolved_type_param.rs +++ b/src/test/ui/async-await/unresolved_type_param.rs @@ -8,24 +8,8 @@ async fn bar() -> () {} async fn foo() { bar().await; //~^ ERROR type inside `async fn` body must be known in this context - //~| ERROR type inside `async fn` body must be known in this context - //~| ERROR type inside `async fn` body must be known in this context - //~| ERROR type inside `async fn` body must be known in this context - //~| ERROR type inside `async fn` body must be known in this context //~| NOTE cannot infer type for type parameter `T` - //~| NOTE cannot infer type for type parameter `T` - //~| NOTE cannot infer type for type parameter `T` - //~| NOTE cannot infer type for type parameter `T` - //~| NOTE cannot infer type for type parameter `T` - //~| NOTE the type is part of the `async fn` body because of this `await` - //~| NOTE the type is part of the `async fn` body because of this `await` //~| NOTE the type is part of the `async fn` body because of this `await` - //~| NOTE the type is part of the `async fn` body because of this `await` - //~| NOTE the type is part of the `async fn` body because of this `await` - //~| NOTE in this expansion of desugaring of `await` - //~| NOTE in this expansion of desugaring of `await` - //~| NOTE in this expansion of desugaring of `await` - //~| NOTE in this expansion of desugaring of `await` //~| NOTE in this expansion of desugaring of `await` } fn main() {} diff --git a/src/test/ui/async-await/unresolved_type_param.stderr b/src/test/ui/async-await/unresolved_type_param.stderr index 8c0ecb8785d33..853e53ed69df2 100644 --- a/src/test/ui/async-await/unresolved_type_param.stderr +++ b/src/test/ui/async-await/unresolved_type_param.stderr @@ -10,54 +10,6 @@ note: the type is part of the `async fn` body because of this `await` LL | bar().await; | ^^^^^^ -error[E0698]: type inside `async fn` body must be known in this context - --> $DIR/unresolved_type_param.rs:9:5 - | -LL | bar().await; - | ^^^ cannot infer type for type parameter `T` declared on the function `bar` - | -note: the type is part of the `async fn` body because of this `await` - --> $DIR/unresolved_type_param.rs:9:10 - | -LL | bar().await; - | ^^^^^^ - -error[E0698]: type inside `async fn` body must be known in this context - --> $DIR/unresolved_type_param.rs:9:5 - | -LL | bar().await; - | ^^^ cannot infer type for type parameter `T` declared on the function `bar` - | -note: the type is part of the `async fn` body because of this `await` - --> $DIR/unresolved_type_param.rs:9:10 - | -LL | bar().await; - | ^^^^^^ - -error[E0698]: type inside `async fn` body must be known in this context - --> $DIR/unresolved_type_param.rs:9:5 - | -LL | bar().await; - | ^^^ cannot infer type for type parameter `T` declared on the function `bar` - | -note: the type is part of the `async fn` body because of this `await` - --> $DIR/unresolved_type_param.rs:9:10 - | -LL | bar().await; - | ^^^^^^ - -error[E0698]: type inside `async fn` body must be known in this context - --> $DIR/unresolved_type_param.rs:9:5 - | -LL | bar().await; - | ^^^ cannot infer type for type parameter `T` declared on the function `bar` - | -note: the type is part of the `async fn` body because of this `await` - --> $DIR/unresolved_type_param.rs:9:10 - | -LL | bar().await; - | ^^^^^^ - -error: aborting due to 5 previous errors +error: aborting due to previous error For more information about this error, try `rustc --explain E0698`. diff --git a/src/test/ui/generator/issue-57478.rs b/src/test/ui/generator/issue-57478.rs index 592632cd35102..39710febdb95c 100644 --- a/src/test/ui/generator/issue-57478.rs +++ b/src/test/ui/generator/issue-57478.rs @@ -1,3 +1,5 @@ +// check-pass + #![feature(negative_impls, generators)] struct Foo; From c4dee401700170c95c649682d62ad150d6b5fdeb Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Fri, 22 Oct 2021 12:45:02 -0700 Subject: [PATCH 03/35] Track drops across multiple yields --- compiler/rustc_middle/src/middle/region.rs | 6 +-- compiler/rustc_passes/src/region.rs | 11 ++++- .../src/check/generator_interior.rs | 46 +++++++++---------- src/test/ui/generator/drop-yield-twice.rs | 15 ++++++ src/test/ui/generator/drop-yield-twice.stderr | 25 ++++++++++ 5 files changed, 75 insertions(+), 28 deletions(-) create mode 100644 src/test/ui/generator/drop-yield-twice.rs create mode 100644 src/test/ui/generator/drop-yield-twice.stderr diff --git a/compiler/rustc_middle/src/middle/region.rs b/compiler/rustc_middle/src/middle/region.rs index 39ca41c92ff75..75dd223d014d4 100644 --- a/compiler/rustc_middle/src/middle/region.rs +++ b/compiler/rustc_middle/src/middle/region.rs @@ -308,7 +308,7 @@ pub struct ScopeTree { /// The reason is that semantically, until the `box` expression returns, /// the values are still owned by their containing expressions. So /// we'll see that `&x`. - pub yield_in_scope: FxHashMap, + pub yield_in_scope: FxHashMap>, /// The number of visit_expr and visit_pat calls done in the body. /// Used to sanity check visit_expr/visit_pat call count when @@ -423,8 +423,8 @@ impl ScopeTree { /// Checks whether the given scope contains a `yield`. If so, /// returns `Some(YieldData)`. If not, returns `None`. - pub fn yield_in_scope(&self, scope: Scope) -> Option { - self.yield_in_scope.get(&scope).cloned() + pub fn yield_in_scope(&self, scope: Scope) -> Option<&Vec> { + self.yield_in_scope.get(&scope) } /// Gives the number of expressions visited in a body. diff --git a/compiler/rustc_passes/src/region.rs b/compiler/rustc_passes/src/region.rs index f8989200d7e08..8b22c46f01ba6 100644 --- a/compiler/rustc_passes/src/region.rs +++ b/compiler/rustc_passes/src/region.rs @@ -365,7 +365,8 @@ fn resolve_expr<'tcx>(visitor: &mut RegionResolutionVisitor<'tcx>, expr: &'tcx h let target_scopes = visitor.fixup_scopes.drain(start_point..); for scope in target_scopes { - let mut yield_data = visitor.scope_tree.yield_in_scope.get_mut(&scope).unwrap(); + let mut yield_data = + visitor.scope_tree.yield_in_scope.get_mut(&scope).unwrap().last_mut().unwrap(); let count = yield_data.expr_and_pat_count; let span = yield_data.span; @@ -428,7 +429,13 @@ fn resolve_expr<'tcx>(visitor: &mut RegionResolutionVisitor<'tcx>, expr: &'tcx h }; let data = YieldData { span, expr_and_pat_count: visitor.expr_and_pat_count, source: *source }; - visitor.scope_tree.yield_in_scope.insert(scope, data); + match visitor.scope_tree.yield_in_scope.get_mut(&scope) { + Some(yields) => yields.push(data), + None => { + visitor.scope_tree.yield_in_scope.insert(scope, vec![data]); + } + } + if visitor.pessimistic_yield { debug!("resolve_expr in pessimistic_yield - marking scope {:?} for fixup", scope); visitor.fixup_scopes.push(scope); diff --git a/compiler/rustc_typeck/src/check/generator_interior.rs b/compiler/rustc_typeck/src/check/generator_interior.rs index a406a1a8ecd99..ba4080031a213 100644 --- a/compiler/rustc_typeck/src/check/generator_interior.rs +++ b/compiler/rustc_typeck/src/check/generator_interior.rs @@ -69,29 +69,29 @@ impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> { // // See the mega-comment at `yield_in_scope` for a proof. - debug!( - "comparing counts yield: {} self: {}, source_span = {:?}", - yield_data.expr_and_pat_count, self.expr_count, source_span - ); - - match self.drop_ranges.get(&hir_id) { - Some(range) if range.contains(yield_data.expr_and_pat_count) => { - debug!("value is dropped at yield point; not recording"); - return None - } - _ => (), - } - - // If it is a borrowing happening in the guard, - // it needs to be recorded regardless because they - // do live across this yield point. - if guard_borrowing_from_pattern - || yield_data.expr_and_pat_count >= self.expr_count - { - Some(yield_data) - } else { - None - } + yield_data + .iter() + .find(|yield_data| { + debug!( + "comparing counts yield: {} self: {}, source_span = {:?}", + yield_data.expr_and_pat_count, self.expr_count, source_span + ); + + match self.drop_ranges.get(&hir_id) { + Some(range) if range.contains(yield_data.expr_and_pat_count) => { + debug!("value is dropped at yield point; not recording"); + return false; + } + _ => (), + } + + // If it is a borrowing happening in the guard, + // it needs to be recorded regardless because they + // do live across this yield point. + guard_borrowing_from_pattern + || yield_data.expr_and_pat_count >= self.expr_count + }) + .cloned() }) }) .unwrap_or_else(|| { diff --git a/src/test/ui/generator/drop-yield-twice.rs b/src/test/ui/generator/drop-yield-twice.rs new file mode 100644 index 0000000000000..f484cbb8d67d5 --- /dev/null +++ b/src/test/ui/generator/drop-yield-twice.rs @@ -0,0 +1,15 @@ +#![feature(negative_impls, generators)] + +struct Foo(i32); +impl !Send for Foo {} + +fn main() { + assert_send(|| { //~ ERROR generator cannot be sent between threads safely + let guard = Foo(42); + yield; + drop(guard); + yield; + }) +} + +fn assert_send(_: T) {} diff --git a/src/test/ui/generator/drop-yield-twice.stderr b/src/test/ui/generator/drop-yield-twice.stderr new file mode 100644 index 0000000000000..f821f2f40055f --- /dev/null +++ b/src/test/ui/generator/drop-yield-twice.stderr @@ -0,0 +1,25 @@ +error: generator cannot be sent between threads safely + --> $DIR/drop-yield-twice.rs:7:5 + | +LL | assert_send(|| { + | ^^^^^^^^^^^ generator is not `Send` + | + = help: within `[generator@$DIR/drop-yield-twice.rs:7:17: 12:6]`, the trait `Send` is not implemented for `Foo` +note: generator is not `Send` as this value is used across a yield + --> $DIR/drop-yield-twice.rs:9:9 + | +LL | let guard = Foo(42); + | ----- has type `Foo` which is not `Send` +LL | yield; + | ^^^^^ yield occurs here, with `guard` maybe used later +... +LL | }) + | - `guard` is later dropped here +note: required by a bound in `assert_send` + --> $DIR/drop-yield-twice.rs:15:19 + | +LL | fn assert_send(_: T) {} + | ^^^^ required by this bound in `assert_send` + +error: aborting due to previous error + From f664cfc47cdfaa83a1fd35e6e6a3fcdb692286ae Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Fri, 22 Oct 2021 15:49:38 -0700 Subject: [PATCH 04/35] Make generator and async-await tests pass The main change needed to make this work is to do a pessimistic over- approximation for AssignOps. The existing ScopeTree analysis in region.rs works by doing both left to right and right to left order and then choosing the most conservative ordering. This behavior is needed because AssignOp's evaluation order depends on whether it is a primitive type or an overloaded operator, which runs as a method call. This change mimics the same behavior as region.rs in generator_interior.rs. Issue #57478 --- .../src/check/generator_interior.rs | 139 +++++++++++++----- src/test/ui/async-await/async-fn-nonsend.rs | 5 +- 2 files changed, 106 insertions(+), 38 deletions(-) diff --git a/compiler/rustc_typeck/src/check/generator_interior.rs b/compiler/rustc_typeck/src/check/generator_interior.rs index ba4080031a213..baeb78139ac96 100644 --- a/compiler/rustc_typeck/src/check/generator_interior.rs +++ b/compiler/rustc_typeck/src/check/generator_interior.rs @@ -15,6 +15,7 @@ use rustc_hir::def_id::DefId; use rustc_hir::hir_id::HirIdSet; use rustc_hir::intravisit::{self, Visitor}; use rustc_hir::{Arm, Expr, ExprKind, Guard, HirId, Pat, PatKind}; +use rustc_middle::hir::place::{Place, PlaceBase}; use rustc_middle::middle::region::{self, YieldData}; use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_span::symbol::sym; @@ -222,30 +223,37 @@ pub fn resolve_interior<'a, 'tcx>( ) { let body = fcx.tcx.hir().body(body_id); - let mut drop_range_visitor = DropRangeVisitor::default(); - - // Run ExprUseVisitor to find where values are consumed. - ExprUseVisitor::new( - &mut drop_range_visitor, - &fcx.infcx, - def_id.expect_local(), - fcx.param_env, - &fcx.typeck_results.borrow(), - ) - .consume_body(body); - intravisit::walk_body(&mut drop_range_visitor, body); - - let mut visitor = InteriorVisitor { - fcx, - types: FxIndexSet::default(), - region_scope_tree: fcx.tcx.region_scope_tree(def_id), - expr_count: 0, - kind, - prev_unresolved_span: None, - guard_bindings: <_>::default(), - guard_bindings_set: <_>::default(), - linted_values: <_>::default(), - drop_ranges: drop_range_visitor.drop_ranges, + let mut visitor = { + let mut drop_range_visitor = DropRangeVisitor { + consumed_places: <_>::default(), + borrowed_places: <_>::default(), + drop_ranges: vec![<_>::default()], + expr_count: 0, + }; + + // Run ExprUseVisitor to find where values are consumed. + ExprUseVisitor::new( + &mut drop_range_visitor, + &fcx.infcx, + def_id.expect_local(), + fcx.param_env, + &fcx.typeck_results.borrow(), + ) + .consume_body(body); + intravisit::walk_body(&mut drop_range_visitor, body); + + InteriorVisitor { + fcx, + types: FxIndexSet::default(), + region_scope_tree: fcx.tcx.region_scope_tree(def_id), + expr_count: 0, + kind, + prev_unresolved_span: None, + guard_bindings: <_>::default(), + guard_bindings_set: <_>::default(), + linted_values: <_>::default(), + drop_ranges: drop_range_visitor.drop_ranges.pop().unwrap(), + } }; intravisit::walk_body(&mut visitor, body); @@ -656,17 +664,37 @@ fn check_must_not_suspend_def( } /// This struct facilitates computing the ranges for which a place is uninitialized. -#[derive(Default)] struct DropRangeVisitor { consumed_places: HirIdSet, - drop_ranges: HirIdMap, + borrowed_places: HirIdSet, + drop_ranges: Vec>, expr_count: usize, } impl DropRangeVisitor { fn record_drop(&mut self, hir_id: HirId) { - debug!("marking {:?} as dropped at {}", hir_id, self.expr_count); - self.drop_ranges.insert(hir_id, DropRange { dropped_at: self.expr_count }); + let drop_ranges = self.drop_ranges.last_mut().unwrap(); + if self.borrowed_places.contains(&hir_id) { + debug!("not marking {:?} as dropped because it is borrowed at some point", hir_id); + } else if self.consumed_places.contains(&hir_id) { + debug!("marking {:?} as dropped at {}", hir_id, self.expr_count); + drop_ranges.insert(hir_id, DropRange { dropped_at: self.expr_count }); + } + } + + fn push_drop_scope(&mut self) { + self.drop_ranges.push(<_>::default()); + } + + fn pop_and_merge_drop_scope(&mut self) { + let mut old_last = self.drop_ranges.pop().unwrap(); + let drop_ranges = self.drop_ranges.last_mut().unwrap(); + for (k, v) in old_last.drain() { + match drop_ranges.get(&k).cloned() { + Some(v2) => drop_ranges.insert(k, v.intersect(&v2)), + None => drop_ranges.insert(k, v), + }; + } } /// ExprUseVisitor's consume callback doesn't go deep enough for our purposes in all @@ -685,6 +713,14 @@ impl DropRangeVisitor { } } +fn place_hir_id(place: &Place<'_>) -> Option { + match place.base { + PlaceBase::Rvalue | PlaceBase::StaticItem => None, + PlaceBase::Local(hir_id) + | PlaceBase::Upvar(ty::UpvarId { var_path: ty::UpvarPath { hir_id }, .. }) => Some(hir_id), + } +} + impl<'tcx> expr_use_visitor::Delegate<'tcx> for DropRangeVisitor { fn consume( &mut self, @@ -693,14 +729,16 @@ impl<'tcx> expr_use_visitor::Delegate<'tcx> for DropRangeVisitor { ) { debug!("consume {:?}; diag_expr_id={:?}", place_with_id, diag_expr_id); self.consumed_places.insert(place_with_id.hir_id); + place_hir_id(&place_with_id.place).map(|place| self.consumed_places.insert(place)); } fn borrow( &mut self, - _place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>, + place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>, _diag_expr_id: hir::HirId, _bk: rustc_middle::ty::BorrowKind, ) { + place_hir_id(&place_with_id.place).map(|place| self.borrowed_places.insert(place)); } fn mutate( @@ -726,17 +764,44 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor { NestedVisitorMap::None } - fn visit_expr(&mut self, expr: &Expr<'_>) { - intravisit::walk_expr(self, expr); + fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { + match expr.kind { + ExprKind::AssignOp(_, lhs, rhs) => { + // These operations are weird because their order of evaluation depends on whether + // the operator is overloaded. In a perfect world, we'd just ask the type checker + // whether this is a method call, but we also need to match the expression IDs + // from RegionResolutionVisitor. RegionResolutionVisitor doesn't know the order, + // so it runs both orders and picks the most conservative. We'll mirror that here. + let mut old_count = self.expr_count; + intravisit::walk_expr(self, lhs); + intravisit::walk_expr(self, rhs); + + self.push_drop_scope(); + std::mem::swap(&mut old_count, &mut self.expr_count); + intravisit::walk_expr(self, rhs); + intravisit::walk_expr(self, lhs); + + // We should have visited the same number of expressions in either order. + assert_eq!(old_count, self.expr_count); + + self.pop_and_merge_drop_scope(); + } + _ => intravisit::walk_expr(self, expr), + } self.expr_count += 1; + self.consume_expr(expr); + } - if self.consumed_places.contains(&expr.hir_id) { - self.consume_expr(expr); - } + fn visit_pat(&mut self, pat: &'tcx Pat<'tcx>) { + intravisit::walk_pat(self, pat); + + // Increment expr_count here to match what InteriorVisitor expects. + self.expr_count += 1; } } +#[derive(Clone)] struct DropRange { /// The post-order id of the point where this expression is dropped. /// @@ -745,7 +810,11 @@ struct DropRange { } impl DropRange { + fn intersect(&self, other: &Self) -> Self { + Self { dropped_at: self.dropped_at.max(other.dropped_at) } + } + fn contains(&self, id: usize) -> bool { - id >= self.dropped_at + id > self.dropped_at } } diff --git a/src/test/ui/async-await/async-fn-nonsend.rs b/src/test/ui/async-await/async-fn-nonsend.rs index 4dd36e7f0f062..210d9ff3f2d32 100644 --- a/src/test/ui/async-await/async-fn-nonsend.rs +++ b/src/test/ui/async-await/async-fn-nonsend.rs @@ -36,7 +36,7 @@ async fn non_send_temporary_in_match() { } async fn non_sync_with_method_call() { - + // FIXME: it would be nice for this to work let f: &mut std::fmt::Formatter = panic!(); if non_sync().fmt(f).unwrap() == () { fut().await; @@ -47,9 +47,8 @@ fn assert_send(_: impl Send) {} pub fn pass_assert() { assert_send(local_dropped_before_await()); - assert_send(non_send_temporary_in_match()); //~^ ERROR future cannot be sent between threads safely assert_send(non_sync_with_method_call()); - + //~^ ERROR future cannot be sent between threads safely } From f246c0b116cdbbad570c23c5745aa01f6f3f64a0 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Mon, 25 Oct 2021 17:01:24 -0700 Subject: [PATCH 05/35] Attribute drop to parent expression of the consume point This is needed to handle cases like `[a, b.await, c]`. `ExprUseVisitor` considers `a` to be consumed when it is passed to the array, but the array is not quite live yet at that point. This means we were missing the `a` value across the await point. Attributing drops to the parent expression means we do not consider the value consumed until the consuming expression has finished. Issue #57478 --- .../src/check/generator_interior.rs | 70 +++++++++++++------ .../ui/async-await/unresolved_type_param.rs | 8 +++ .../async-await/unresolved_type_param.stderr | 26 ++++++- src/test/ui/lint/must_not_suspend/dedup.rs | 2 +- .../ui/lint/must_not_suspend/dedup.stderr | 12 ++-- 5 files changed, 87 insertions(+), 31 deletions(-) diff --git a/compiler/rustc_typeck/src/check/generator_interior.rs b/compiler/rustc_typeck/src/check/generator_interior.rs index baeb78139ac96..92dea92a0bcad 100644 --- a/compiler/rustc_typeck/src/check/generator_interior.rs +++ b/compiler/rustc_typeck/src/check/generator_interior.rs @@ -6,7 +6,7 @@ use crate::expr_use_visitor::{self, ExprUseVisitor}; use super::FnCtxt; -use hir::HirIdMap; +use hir::{HirIdMap, Node}; use rustc_data_structures::fx::{FxHashSet, FxIndexSet}; use rustc_errors::pluralize; use rustc_hir as hir; @@ -15,6 +15,7 @@ use rustc_hir::def_id::DefId; use rustc_hir::hir_id::HirIdSet; use rustc_hir::intravisit::{self, Visitor}; use rustc_hir::{Arm, Expr, ExprKind, Guard, HirId, Pat, PatKind}; +use rustc_middle::hir::map::Map; use rustc_middle::hir::place::{Place, PlaceBase}; use rustc_middle::middle::region::{self, YieldData}; use rustc_middle::ty::{self, Ty, TyCtxt}; @@ -225,6 +226,7 @@ pub fn resolve_interior<'a, 'tcx>( let mut visitor = { let mut drop_range_visitor = DropRangeVisitor { + hir: fcx.tcx.hir(), consumed_places: <_>::default(), borrowed_places: <_>::default(), drop_ranges: vec![<_>::default()], @@ -664,19 +666,28 @@ fn check_must_not_suspend_def( } /// This struct facilitates computing the ranges for which a place is uninitialized. -struct DropRangeVisitor { - consumed_places: HirIdSet, +struct DropRangeVisitor<'tcx> { + hir: Map<'tcx>, + /// Maps a HirId to a set of HirIds that are dropped by that node. + consumed_places: HirIdMap, borrowed_places: HirIdSet, drop_ranges: Vec>, expr_count: usize, } -impl DropRangeVisitor { +impl DropRangeVisitor<'tcx> { + fn mark_consumed(&mut self, consumer: HirId, target: HirId) { + if !self.consumed_places.contains_key(&consumer) { + self.consumed_places.insert(consumer, <_>::default()); + } + self.consumed_places.get_mut(&consumer).map(|places| places.insert(target)); + } + fn record_drop(&mut self, hir_id: HirId) { let drop_ranges = self.drop_ranges.last_mut().unwrap(); if self.borrowed_places.contains(&hir_id) { debug!("not marking {:?} as dropped because it is borrowed at some point", hir_id); - } else if self.consumed_places.contains(&hir_id) { + } else { debug!("marking {:?} as dropped at {}", hir_id, self.expr_count); drop_ranges.insert(hir_id, DropRange { dropped_at: self.expr_count }); } @@ -700,15 +711,24 @@ impl DropRangeVisitor { /// ExprUseVisitor's consume callback doesn't go deep enough for our purposes in all /// expressions. This method consumes a little deeper into the expression when needed. fn consume_expr(&mut self, expr: &hir::Expr<'_>) { - self.record_drop(expr.hir_id); - match expr.kind { - hir::ExprKind::Path(hir::QPath::Resolved( - _, - hir::Path { res: hir::def::Res::Local(hir_id), .. }, - )) => { - self.record_drop(*hir_id); + debug!("consuming expr {:?}, count={}", expr.hir_id, self.expr_count); + let places = self + .consumed_places + .get(&expr.hir_id) + .map_or(vec![], |places| places.iter().cloned().collect()); + for place in places { + self.record_drop(place); + if let Some(Node::Expr(expr)) = self.hir.find(place) { + match expr.kind { + hir::ExprKind::Path(hir::QPath::Resolved( + _, + hir::Path { res: hir::def::Res::Local(hir_id), .. }, + )) => { + self.record_drop(*hir_id); + } + _ => (), + } } - _ => (), } } } @@ -721,15 +741,19 @@ fn place_hir_id(place: &Place<'_>) -> Option { } } -impl<'tcx> expr_use_visitor::Delegate<'tcx> for DropRangeVisitor { +impl<'tcx> expr_use_visitor::Delegate<'tcx> for DropRangeVisitor<'tcx> { fn consume( &mut self, place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>, diag_expr_id: hir::HirId, ) { - debug!("consume {:?}; diag_expr_id={:?}", place_with_id, diag_expr_id); - self.consumed_places.insert(place_with_id.hir_id); - place_hir_id(&place_with_id.place).map(|place| self.consumed_places.insert(place)); + let parent = match self.hir.find_parent_node(place_with_id.hir_id) { + Some(parent) => parent, + None => place_with_id.hir_id, + }; + debug!("consume {:?}; diag_expr_id={:?}, using parent {:?}", place_with_id, diag_expr_id, parent); + self.mark_consumed(parent, place_with_id.hir_id); + place_hir_id(&place_with_id.place).map(|place| self.mark_consumed(parent, place)); } fn borrow( @@ -757,7 +781,7 @@ impl<'tcx> expr_use_visitor::Delegate<'tcx> for DropRangeVisitor { } } -impl<'tcx> Visitor<'tcx> for DropRangeVisitor { +impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { type Map = intravisit::ErasedMap<'tcx>; fn nested_visit_map(&mut self) -> NestedVisitorMap { @@ -766,20 +790,20 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor { fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { match expr.kind { - ExprKind::AssignOp(_, lhs, rhs) => { + ExprKind::AssignOp(_op, lhs, rhs) => { // These operations are weird because their order of evaluation depends on whether // the operator is overloaded. In a perfect world, we'd just ask the type checker // whether this is a method call, but we also need to match the expression IDs // from RegionResolutionVisitor. RegionResolutionVisitor doesn't know the order, // so it runs both orders and picks the most conservative. We'll mirror that here. let mut old_count = self.expr_count; - intravisit::walk_expr(self, lhs); - intravisit::walk_expr(self, rhs); + self.visit_expr(lhs); + self.visit_expr(rhs); self.push_drop_scope(); std::mem::swap(&mut old_count, &mut self.expr_count); - intravisit::walk_expr(self, rhs); - intravisit::walk_expr(self, lhs); + self.visit_expr(rhs); + self.visit_expr(lhs); // We should have visited the same number of expressions in either order. assert_eq!(old_count, self.expr_count); diff --git a/src/test/ui/async-await/unresolved_type_param.rs b/src/test/ui/async-await/unresolved_type_param.rs index 79c043b701ddb..d313691b38857 100644 --- a/src/test/ui/async-await/unresolved_type_param.rs +++ b/src/test/ui/async-await/unresolved_type_param.rs @@ -8,8 +8,16 @@ async fn bar() -> () {} async fn foo() { bar().await; //~^ ERROR type inside `async fn` body must be known in this context + //~| ERROR type inside `async fn` body must be known in this context + //~| ERROR type inside `async fn` body must be known in this context //~| NOTE cannot infer type for type parameter `T` + //~| NOTE cannot infer type for type parameter `T` + //~| NOTE cannot infer type for type parameter `T` + //~| NOTE the type is part of the `async fn` body because of this `await` //~| NOTE the type is part of the `async fn` body because of this `await` + //~| NOTE the type is part of the `async fn` body because of this `await` + //~| NOTE in this expansion of desugaring of `await` + //~| NOTE in this expansion of desugaring of `await` //~| NOTE in this expansion of desugaring of `await` } fn main() {} diff --git a/src/test/ui/async-await/unresolved_type_param.stderr b/src/test/ui/async-await/unresolved_type_param.stderr index 853e53ed69df2..6a268bcda6297 100644 --- a/src/test/ui/async-await/unresolved_type_param.stderr +++ b/src/test/ui/async-await/unresolved_type_param.stderr @@ -10,6 +10,30 @@ note: the type is part of the `async fn` body because of this `await` LL | bar().await; | ^^^^^^ -error: aborting due to previous error +error[E0698]: type inside `async fn` body must be known in this context + --> $DIR/unresolved_type_param.rs:9:5 + | +LL | bar().await; + | ^^^ cannot infer type for type parameter `T` declared on the function `bar` + | +note: the type is part of the `async fn` body because of this `await` + --> $DIR/unresolved_type_param.rs:9:5 + | +LL | bar().await; + | ^^^^^^^^^^^ + +error[E0698]: type inside `async fn` body must be known in this context + --> $DIR/unresolved_type_param.rs:9:5 + | +LL | bar().await; + | ^^^ cannot infer type for type parameter `T` declared on the function `bar` + | +note: the type is part of the `async fn` body because of this `await` + --> $DIR/unresolved_type_param.rs:9:5 + | +LL | bar().await; + | ^^^^^^^^^^^ + +error: aborting due to 3 previous errors For more information about this error, try `rustc --explain E0698`. diff --git a/src/test/ui/lint/must_not_suspend/dedup.rs b/src/test/ui/lint/must_not_suspend/dedup.rs index 040fff5a5a5a8..81a08579bb7bc 100644 --- a/src/test/ui/lint/must_not_suspend/dedup.rs +++ b/src/test/ui/lint/must_not_suspend/dedup.rs @@ -13,7 +13,7 @@ async fn wheeee(t: T) { } async fn yes() { - wheeee(No {}).await; //~ ERROR `No` held across + wheeee(&No {}).await; //~ ERROR `No` held across } fn main() { diff --git a/src/test/ui/lint/must_not_suspend/dedup.stderr b/src/test/ui/lint/must_not_suspend/dedup.stderr index bc1b611299a2b..d15137474527e 100644 --- a/src/test/ui/lint/must_not_suspend/dedup.stderr +++ b/src/test/ui/lint/must_not_suspend/dedup.stderr @@ -1,8 +1,8 @@ error: `No` held across a suspend point, but should not be - --> $DIR/dedup.rs:16:12 + --> $DIR/dedup.rs:16:13 | -LL | wheeee(No {}).await; - | ^^^^^ ------ the value is held across this suspend point +LL | wheeee(&No {}).await; + | --------^^^^^------- the value is held across this suspend point | note: the lint level is defined here --> $DIR/dedup.rs:3:9 @@ -10,10 +10,10 @@ note: the lint level is defined here LL | #![deny(must_not_suspend)] | ^^^^^^^^^^^^^^^^ help: consider using a block (`{ ... }`) to shrink the value's scope, ending before the suspend point - --> $DIR/dedup.rs:16:12 + --> $DIR/dedup.rs:16:13 | -LL | wheeee(No {}).await; - | ^^^^^ +LL | wheeee(&No {}).await; + | ^^^^^ error: aborting due to previous error From aa029d4bbe78fafbffdebb398a767941459d9d4e Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Wed, 27 Oct 2021 17:46:08 -0700 Subject: [PATCH 06/35] Support conditional drops This adds support for branching and merging control flow and uses this to correctly handle the case where a value is dropped in one branch of an if expression but not another. There are other cases we need to handle, which will come in follow up patches. Issue #57478 --- Cargo.lock | 1 + compiler/rustc_typeck/Cargo.toml | 1 + .../src/check/generator_interior.rs | 224 +++++++++++++++--- src/test/ui/generator/drop-if.rs | 22 ++ 4 files changed, 220 insertions(+), 28 deletions(-) create mode 100644 src/test/ui/generator/drop-if.rs diff --git a/Cargo.lock b/Cargo.lock index 529e17b158fc8..dfe3db5907a33 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4383,6 +4383,7 @@ dependencies = [ name = "rustc_typeck" version = "0.0.0" dependencies = [ + "itertools 0.9.0", "rustc_arena", "rustc_ast", "rustc_attr", diff --git a/compiler/rustc_typeck/Cargo.toml b/compiler/rustc_typeck/Cargo.toml index 7e570e151c529..1da106d7002ef 100644 --- a/compiler/rustc_typeck/Cargo.toml +++ b/compiler/rustc_typeck/Cargo.toml @@ -8,6 +8,7 @@ test = false doctest = false [dependencies] +itertools = "0.9" rustc_arena = { path = "../rustc_arena" } tracing = "0.1" rustc_macros = { path = "../rustc_macros" } diff --git a/compiler/rustc_typeck/src/check/generator_interior.rs b/compiler/rustc_typeck/src/check/generator_interior.rs index 92dea92a0bcad..6144cbbd8dd4b 100644 --- a/compiler/rustc_typeck/src/check/generator_interior.rs +++ b/compiler/rustc_typeck/src/check/generator_interior.rs @@ -3,10 +3,13 @@ //! is calculated in `rustc_const_eval::transform::generator` and may be a subset of the //! types computed here. +use std::mem; + use crate::expr_use_visitor::{self, ExprUseVisitor}; use super::FnCtxt; use hir::{HirIdMap, Node}; +use itertools::Itertools; use rustc_data_structures::fx::{FxHashSet, FxIndexSet}; use rustc_errors::pluralize; use rustc_hir as hir; @@ -24,6 +27,9 @@ use rustc_span::Span; use smallvec::SmallVec; use tracing::debug; +#[cfg(test)] +mod tests; + struct InteriorVisitor<'a, 'tcx> { fcx: &'a FnCtxt<'a, 'tcx>, types: FxIndexSet>, @@ -80,7 +86,9 @@ impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> { ); match self.drop_ranges.get(&hir_id) { - Some(range) if range.contains(yield_data.expr_and_pat_count) => { + Some(range) + if range.is_dropped_at(yield_data.expr_and_pat_count) => + { debug!("value is dropped at yield point; not recording"); return false; } @@ -229,7 +237,7 @@ pub fn resolve_interior<'a, 'tcx>( hir: fcx.tcx.hir(), consumed_places: <_>::default(), borrowed_places: <_>::default(), - drop_ranges: vec![<_>::default()], + drop_ranges: <_>::default(), expr_count: 0, }; @@ -254,7 +262,7 @@ pub fn resolve_interior<'a, 'tcx>( guard_bindings: <_>::default(), guard_bindings_set: <_>::default(), linted_values: <_>::default(), - drop_ranges: drop_range_visitor.drop_ranges.pop().unwrap(), + drop_ranges: drop_range_visitor.drop_ranges, } }; intravisit::walk_body(&mut visitor, body); @@ -671,7 +679,7 @@ struct DropRangeVisitor<'tcx> { /// Maps a HirId to a set of HirIds that are dropped by that node. consumed_places: HirIdMap, borrowed_places: HirIdSet, - drop_ranges: Vec>, + drop_ranges: HirIdMap, expr_count: usize, } @@ -684,28 +692,42 @@ impl DropRangeVisitor<'tcx> { } fn record_drop(&mut self, hir_id: HirId) { - let drop_ranges = self.drop_ranges.last_mut().unwrap(); + let drop_ranges = &mut self.drop_ranges; if self.borrowed_places.contains(&hir_id) { debug!("not marking {:?} as dropped because it is borrowed at some point", hir_id); } else { debug!("marking {:?} as dropped at {}", hir_id, self.expr_count); - drop_ranges.insert(hir_id, DropRange { dropped_at: self.expr_count }); + drop_ranges.insert(hir_id, DropRange::new(self.expr_count)); } } - fn push_drop_scope(&mut self) { - self.drop_ranges.push(<_>::default()); + fn swap_drop_ranges(&mut self, mut other: HirIdMap) -> HirIdMap { + mem::swap(&mut self.drop_ranges, &mut other); + other } - fn pop_and_merge_drop_scope(&mut self) { - let mut old_last = self.drop_ranges.pop().unwrap(); - let drop_ranges = self.drop_ranges.last_mut().unwrap(); - for (k, v) in old_last.drain() { - match drop_ranges.get(&k).cloned() { - Some(v2) => drop_ranges.insert(k, v.intersect(&v2)), - None => drop_ranges.insert(k, v), - }; - } + #[allow(dead_code)] + fn fork_drop_ranges(&self) -> HirIdMap { + self.drop_ranges.iter().map(|(k, v)| (*k, v.fork_at(self.expr_count))).collect() + } + + fn intersect_drop_ranges(&mut self, drops: HirIdMap) { + drops.into_iter().for_each(|(k, v)| match self.drop_ranges.get_mut(&k) { + Some(ranges) => *ranges = ranges.intersect(&v), + None => { + self.drop_ranges.insert(k, v); + } + }) + } + + #[allow(dead_code)] + fn merge_drop_ranges(&mut self, drops: HirIdMap) { + drops.into_iter().for_each(|(k, v)| { + if !self.drop_ranges.contains_key(&k) { + self.drop_ranges.insert(k, DropRange { events: vec![] }); + } + self.drop_ranges.get_mut(&k).unwrap().merge_with(&v, self.expr_count); + }); } /// ExprUseVisitor's consume callback doesn't go deep enough for our purposes in all @@ -751,7 +773,10 @@ impl<'tcx> expr_use_visitor::Delegate<'tcx> for DropRangeVisitor<'tcx> { Some(parent) => parent, None => place_with_id.hir_id, }; - debug!("consume {:?}; diag_expr_id={:?}, using parent {:?}", place_with_id, diag_expr_id, parent); + debug!( + "consume {:?}; diag_expr_id={:?}, using parent {:?}", + place_with_id, diag_expr_id, parent + ); self.mark_consumed(parent, place_with_id.hir_id); place_hir_id(&place_with_id.place).map(|place| self.mark_consumed(parent, place)); } @@ -800,7 +825,7 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { self.visit_expr(lhs); self.visit_expr(rhs); - self.push_drop_scope(); + let old_drops = self.swap_drop_ranges(<_>::default()); std::mem::swap(&mut old_count, &mut self.expr_count); self.visit_expr(rhs); self.visit_expr(lhs); @@ -808,7 +833,39 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { // We should have visited the same number of expressions in either order. assert_eq!(old_count, self.expr_count); - self.pop_and_merge_drop_scope(); + self.intersect_drop_ranges(old_drops); + } + ExprKind::If(test, if_true, if_false) => { + self.visit_expr(test); + + match if_false { + Some(if_false) => { + let mut true_ranges = self.fork_drop_ranges(); + let mut false_ranges = self.fork_drop_ranges(); + + true_ranges = self.swap_drop_ranges(true_ranges); + self.visit_expr(if_true); + true_ranges = self.swap_drop_ranges(true_ranges); + + false_ranges = self.swap_drop_ranges(false_ranges); + self.visit_expr(if_false); + false_ranges = self.swap_drop_ranges(false_ranges); + + self.merge_drop_ranges(true_ranges); + self.merge_drop_ranges(false_ranges); + } + None => { + let mut true_ranges = self.fork_drop_ranges(); + debug!("true branch drop range fork: {:?}", true_ranges); + true_ranges = self.swap_drop_ranges(true_ranges); + self.visit_expr(if_true); + true_ranges = self.swap_drop_ranges(true_ranges); + debug!("true branch computed drop_ranges: {:?}", true_ranges); + debug!("drop ranges before merging: {:?}", self.drop_ranges); + self.merge_drop_ranges(true_ranges); + debug!("drop ranges after merging: {:?}", self.drop_ranges); + } + } } _ => intravisit::walk_expr(self, expr), } @@ -825,20 +882,131 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { } } -#[derive(Clone)] +#[derive(Clone, Debug, PartialEq, Eq)] +enum Event { + Drop(usize), + Reinit(usize), +} + +impl Event { + fn location(&self) -> usize { + match *self { + Event::Drop(i) | Event::Reinit(i) => i, + } + } +} + +impl PartialOrd for Event { + fn partial_cmp(&self, other: &Self) -> Option { + self.location().partial_cmp(&other.location()) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] struct DropRange { - /// The post-order id of the point where this expression is dropped. - /// - /// We can consider the value dropped at any post-order id greater than dropped_at. - dropped_at: usize, + events: Vec, } impl DropRange { + fn new(begin: usize) -> Self { + Self { events: vec![Event::Drop(begin)] } + } + fn intersect(&self, other: &Self) -> Self { - Self { dropped_at: self.dropped_at.max(other.dropped_at) } + let mut events = vec![]; + self.events + .iter() + .merge_join_by(other.events.iter(), |a, b| a.partial_cmp(b).unwrap()) + .fold((false, false), |(left, right), event| match event { + itertools::EitherOrBoth::Both(_, _) => todo!(), + itertools::EitherOrBoth::Left(e) => match e { + Event::Drop(i) => { + if !left && right { + events.push(Event::Drop(*i)); + } + (true, right) + } + Event::Reinit(i) => { + if left && !right { + events.push(Event::Reinit(*i)); + } + (false, right) + } + }, + itertools::EitherOrBoth::Right(e) => match e { + Event::Drop(i) => { + if left && !right { + events.push(Event::Drop(*i)); + } + (left, true) + } + Event::Reinit(i) => { + if !left && right { + events.push(Event::Reinit(*i)); + } + (left, false) + } + }, + }); + Self { events } + } + + fn is_dropped_at(&self, id: usize) -> bool { + match self.events.iter().try_fold(false, |is_dropped, event| { + if event.location() < id { + Ok(match event { + Event::Drop(_) => true, + Event::Reinit(_) => false, + }) + } else { + Err(is_dropped) + } + }) { + Ok(is_dropped) | Err(is_dropped) => is_dropped, + } + } + + #[allow(dead_code)] + fn drop(&mut self, location: usize) { + self.events.push(Event::Drop(location)) + } + + #[allow(dead_code)] + fn reinit(&mut self, location: usize) { + self.events.push(Event::Reinit(location)); + } + + /// Merges another range with this one. Meant to be used at control flow join points. + /// + /// After merging, the value will be dead at the end of the range only if it was dead + /// at the end of both self and other. + /// + /// Assumes that all locations in each range are less than joinpoint + #[allow(dead_code)] + fn merge_with(&mut self, other: &DropRange, join_point: usize) { + let mut events: Vec<_> = + self.events.iter().merge(other.events.iter()).dedup().cloned().collect(); + + events.push(if self.is_dropped_at(join_point) && other.is_dropped_at(join_point) { + Event::Drop(join_point) + } else { + Event::Reinit(join_point) + }); + + self.events = events; } - fn contains(&self, id: usize) -> bool { - id > self.dropped_at + /// Creates a new DropRange from this one at the split point. + /// + /// Used to model branching control flow. + #[allow(dead_code)] + fn fork_at(&self, split_point: usize) -> Self { + Self { + events: vec![if self.is_dropped_at(split_point) { + Event::Drop(split_point) + } else { + Event::Reinit(split_point) + }], + } } } diff --git a/src/test/ui/generator/drop-if.rs b/src/test/ui/generator/drop-if.rs new file mode 100644 index 0000000000000..40f01f78662a3 --- /dev/null +++ b/src/test/ui/generator/drop-if.rs @@ -0,0 +1,22 @@ +// build-pass + +// This test case is reduced from src/test/ui/drop/dynamic-drop-async.rs + +#![feature(generators)] + +struct Ptr; +impl<'a> Drop for Ptr { + fn drop(&mut self) { + } +} + +fn main() { + let arg = true; + let _ = || { + let arr = [Ptr]; + if arg { + drop(arr); + } + yield + }; +} From 96117701f94a2c08235a87fce9d362ca26997017 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Wed, 3 Nov 2021 16:28:07 -0700 Subject: [PATCH 07/35] Support reinitialization of variables --- .../src/check/generator_interior.rs | 45 ++++++++--- src/test/ui/generator/drop-control-flow.rs | 81 +++++++++++++++++++ src/test/ui/generator/drop-if.rs | 22 ----- 3 files changed, 116 insertions(+), 32 deletions(-) create mode 100644 src/test/ui/generator/drop-control-flow.rs delete mode 100644 src/test/ui/generator/drop-if.rs diff --git a/compiler/rustc_typeck/src/check/generator_interior.rs b/compiler/rustc_typeck/src/check/generator_interior.rs index 6144cbbd8dd4b..65644a54c4fa1 100644 --- a/compiler/rustc_typeck/src/check/generator_interior.rs +++ b/compiler/rustc_typeck/src/check/generator_interior.rs @@ -691,13 +691,20 @@ impl DropRangeVisitor<'tcx> { self.consumed_places.get_mut(&consumer).map(|places| places.insert(target)); } + fn drop_range(&mut self, hir_id: &HirId) -> &mut DropRange { + if !self.drop_ranges.contains_key(hir_id) { + self.drop_ranges.insert(*hir_id, DropRange::empty()); + } + self.drop_ranges.get_mut(hir_id).unwrap() + } + fn record_drop(&mut self, hir_id: HirId) { - let drop_ranges = &mut self.drop_ranges; if self.borrowed_places.contains(&hir_id) { debug!("not marking {:?} as dropped because it is borrowed at some point", hir_id); } else { debug!("marking {:?} as dropped at {}", hir_id, self.expr_count); - drop_ranges.insert(hir_id, DropRange::new(self.expr_count)); + let count = self.expr_count; + self.drop_range(&hir_id).drop(count); } } @@ -706,7 +713,6 @@ impl DropRangeVisitor<'tcx> { other } - #[allow(dead_code)] fn fork_drop_ranges(&self) -> HirIdMap { self.drop_ranges.iter().map(|(k, v)| (*k, v.fork_at(self.expr_count))).collect() } @@ -720,7 +726,6 @@ impl DropRangeVisitor<'tcx> { }) } - #[allow(dead_code)] fn merge_drop_ranges(&mut self, drops: HirIdMap) { drops.into_iter().for_each(|(k, v)| { if !self.drop_ranges.contains_key(&k) { @@ -753,6 +758,20 @@ impl DropRangeVisitor<'tcx> { } } } + + fn reinit_expr(&mut self, expr: &hir::Expr<'_>) { + if let ExprKind::Path(hir::QPath::Resolved( + _, + hir::Path { res: hir::def::Res::Local(hir_id), .. }, + )) = expr.kind + { + let location = self.expr_count; + debug!("reinitializing {:?} at {}", hir_id, location); + self.drop_range(hir_id).reinit(location) + } else { + warn!("reinitializing {:?} is not supported", expr); + } + } } fn place_hir_id(place: &Place<'_>) -> Option { @@ -814,6 +833,7 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { } fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { + let mut reinit = None; match expr.kind { ExprKind::AssignOp(_op, lhs, rhs) => { // These operations are weird because their order of evaluation depends on whether @@ -867,11 +887,20 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { } } } + ExprKind::Assign(lhs, rhs, _) => { + self.visit_expr(lhs); + self.visit_expr(rhs); + + reinit = Some(lhs); + } _ => intravisit::walk_expr(self, expr), } self.expr_count += 1; self.consume_expr(expr); + if let Some(expr) = reinit { + self.reinit_expr(expr); + } } fn visit_pat(&mut self, pat: &'tcx Pat<'tcx>) { @@ -908,8 +937,8 @@ struct DropRange { } impl DropRange { - fn new(begin: usize) -> Self { - Self { events: vec![Event::Drop(begin)] } + fn empty() -> Self { + Self { events: vec![] } } fn intersect(&self, other: &Self) -> Self { @@ -966,12 +995,10 @@ impl DropRange { } } - #[allow(dead_code)] fn drop(&mut self, location: usize) { self.events.push(Event::Drop(location)) } - #[allow(dead_code)] fn reinit(&mut self, location: usize) { self.events.push(Event::Reinit(location)); } @@ -982,7 +1009,6 @@ impl DropRange { /// at the end of both self and other. /// /// Assumes that all locations in each range are less than joinpoint - #[allow(dead_code)] fn merge_with(&mut self, other: &DropRange, join_point: usize) { let mut events: Vec<_> = self.events.iter().merge(other.events.iter()).dedup().cloned().collect(); @@ -999,7 +1025,6 @@ impl DropRange { /// Creates a new DropRange from this one at the split point. /// /// Used to model branching control flow. - #[allow(dead_code)] fn fork_at(&self, split_point: usize) -> Self { Self { events: vec![if self.is_dropped_at(split_point) { diff --git a/src/test/ui/generator/drop-control-flow.rs b/src/test/ui/generator/drop-control-flow.rs new file mode 100644 index 0000000000000..b180a61b10410 --- /dev/null +++ b/src/test/ui/generator/drop-control-flow.rs @@ -0,0 +1,81 @@ +// build-pass + +// A test to ensure generators capture values that were conditionally dropped, +// and also that values that are dropped along all paths to a yield do not get +// included in the generator type. + +#![feature(generators, negative_impls)] + +#![allow(unused_assignments, dead_code)] + +struct Ptr; +impl<'a> Drop for Ptr { + fn drop(&mut self) {} +} + +struct NonSend {} +impl !Send for NonSend {} + +fn assert_send(_: T) {} + +// This test case is reduced from src/test/ui/drop/dynamic-drop-async.rs +fn one_armed_if(arg: bool) { + let _ = || { + let arr = [Ptr]; + if arg { + drop(arr); + } + yield; + }; +} + +fn two_armed_if(arg: bool) { + assert_send(|| { + let arr = [Ptr]; + if arg { + drop(arr); + } else { + drop(arr); + } + yield; + }) +} + +fn if_let(arg: Option) { + let _ = || { + let arr = [Ptr]; + if let Some(_) = arg { + drop(arr); + } + yield; + }; +} + +fn reinit() { + let _ = || { + let mut arr = [Ptr]; + drop(arr); + arr = [Ptr]; + yield; + }; +} + +fn loop_uninit() { + let _ = || { + let mut arr = [Ptr]; + let mut count = 0; + drop(arr); + while count < 3 { + yield; + arr = [Ptr]; + count += 1; + } + }; +} + +fn main() { + one_armed_if(true); + if_let(Some(41)); + reinit(); + // loop_uninit(); +} diff --git a/src/test/ui/generator/drop-if.rs b/src/test/ui/generator/drop-if.rs deleted file mode 100644 index 40f01f78662a3..0000000000000 --- a/src/test/ui/generator/drop-if.rs +++ /dev/null @@ -1,22 +0,0 @@ -// build-pass - -// This test case is reduced from src/test/ui/drop/dynamic-drop-async.rs - -#![feature(generators)] - -struct Ptr; -impl<'a> Drop for Ptr { - fn drop(&mut self) { - } -} - -fn main() { - let arg = true; - let _ = || { - let arr = [Ptr]; - if arg { - drop(arr); - } - yield - }; -} From 298ca2f6799201152e2e75a781a7aabb29424aea Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Thu, 4 Nov 2021 10:56:07 -0700 Subject: [PATCH 08/35] Basic loop support --- .../src/check/generator_interior.rs | 36 +++++++++++++++---- src/test/ui/generator/drop-control-flow.rs | 2 +- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_typeck/src/check/generator_interior.rs b/compiler/rustc_typeck/src/check/generator_interior.rs index 65644a54c4fa1..d7ad84684d1f1 100644 --- a/compiler/rustc_typeck/src/check/generator_interior.rs +++ b/compiler/rustc_typeck/src/check/generator_interior.rs @@ -726,15 +726,19 @@ impl DropRangeVisitor<'tcx> { }) } - fn merge_drop_ranges(&mut self, drops: HirIdMap) { + fn merge_drop_ranges_at(&mut self, drops: HirIdMap, join_point: usize) { drops.into_iter().for_each(|(k, v)| { if !self.drop_ranges.contains_key(&k) { self.drop_ranges.insert(k, DropRange { events: vec![] }); } - self.drop_ranges.get_mut(&k).unwrap().merge_with(&v, self.expr_count); + self.drop_ranges.get_mut(&k).unwrap().merge_with(&v, join_point); }); } + fn merge_drop_ranges(&mut self, drops: HirIdMap) { + self.merge_drop_ranges_at(drops, self.expr_count); + } + /// ExprUseVisitor's consume callback doesn't go deep enough for our purposes in all /// expressions. This method consumes a little deeper into the expression when needed. fn consume_expr(&mut self, expr: &hir::Expr<'_>) { @@ -893,6 +897,17 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { reinit = Some(lhs); } + ExprKind::Loop(body, ..) => { + let body_drop_ranges = self.fork_drop_ranges(); + let old_drop_ranges = self.swap_drop_ranges(body_drop_ranges); + + let join_point = self.expr_count; + + self.visit_block(body); + + let body_drop_ranges = self.swap_drop_ranges(old_drop_ranges); + self.merge_drop_ranges_at(body_drop_ranges, join_point); + } _ => intravisit::walk_expr(self, expr), } @@ -1007,11 +1022,20 @@ impl DropRange { /// /// After merging, the value will be dead at the end of the range only if it was dead /// at the end of both self and other. - /// - /// Assumes that all locations in each range are less than joinpoint fn merge_with(&mut self, other: &DropRange, join_point: usize) { - let mut events: Vec<_> = - self.events.iter().merge(other.events.iter()).dedup().cloned().collect(); + let join_event = if self.is_dropped_at(join_point) && other.is_dropped_at(join_point) { + Event::Drop(join_point) + } else { + Event::Reinit(join_point) + }; + let mut events: Vec<_> = self + .events + .iter() + .merge([join_event].iter()) + .merge(other.events.iter()) + .dedup() + .cloned() + .collect(); events.push(if self.is_dropped_at(join_point) && other.is_dropped_at(join_point) { Event::Drop(join_point) diff --git a/src/test/ui/generator/drop-control-flow.rs b/src/test/ui/generator/drop-control-flow.rs index b180a61b10410..6587e54df6083 100644 --- a/src/test/ui/generator/drop-control-flow.rs +++ b/src/test/ui/generator/drop-control-flow.rs @@ -77,5 +77,5 @@ fn main() { one_armed_if(true); if_let(Some(41)); reinit(); - // loop_uninit(); + loop_uninit(); } From 457415294c57dff4b07cc06165eb284d9a14a24a Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Thu, 4 Nov 2021 16:38:47 -0700 Subject: [PATCH 09/35] Handle more cases with conditionally initialized/dropped values --- .../src/check/generator_interior.rs | 48 ++++++++++++++++++- src/test/ui/generator/drop-control-flow.rs | 44 ++++++++++++++++- 2 files changed, 89 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_typeck/src/check/generator_interior.rs b/compiler/rustc_typeck/src/check/generator_interior.rs index d7ad84684d1f1..e7794b199e714 100644 --- a/compiler/rustc_typeck/src/check/generator_interior.rs +++ b/compiler/rustc_typeck/src/check/generator_interior.rs @@ -871,7 +871,8 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { self.visit_expr(if_true); true_ranges = self.swap_drop_ranges(true_ranges); - false_ranges = self.swap_drop_ranges(false_ranges); + false_ranges = + self.swap_drop_ranges(trim_drop_ranges(&false_ranges, self.expr_count)); self.visit_expr(if_false); false_ranges = self.swap_drop_ranges(false_ranges); @@ -908,6 +909,31 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { let body_drop_ranges = self.swap_drop_ranges(old_drop_ranges); self.merge_drop_ranges_at(body_drop_ranges, join_point); } + ExprKind::Match(scrutinee, arms, ..) => { + self.visit_expr(scrutinee); + + let forked_ranges = self.fork_drop_ranges(); + let arm_drops = arms + .iter() + .map(|Arm { hir_id, pat, body, guard, .. }| { + debug!("match arm {:?} starts at {}", hir_id, self.expr_count); + let old_ranges = self + .swap_drop_ranges(trim_drop_ranges(&forked_ranges, self.expr_count)); + self.visit_pat(pat); + match guard { + Some(Guard::If(expr)) => self.visit_expr(expr), + Some(Guard::IfLet(pat, expr)) => { + self.visit_pat(pat); + self.visit_expr(expr); + } + None => (), + } + self.visit_expr(body); + self.swap_drop_ranges(old_ranges) + }) + .collect::>(); + arm_drops.into_iter().for_each(|drops| self.merge_drop_ranges(drops)); + } _ => intravisit::walk_expr(self, expr), } @@ -926,6 +952,10 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { } } +fn trim_drop_ranges(drop_ranges: &HirIdMap, trim_from: usize) -> HirIdMap { + drop_ranges.iter().map(|(k, v)| (*k, v.trimmed(trim_from))).collect() +} + #[derive(Clone, Debug, PartialEq, Eq)] enum Event { Drop(usize), @@ -1058,4 +1088,20 @@ impl DropRange { }], } } + + fn trimmed(&self, trim_from: usize) -> Self { + let start = if self.is_dropped_at(trim_from) { + Event::Drop(trim_from) + } else { + Event::Reinit(trim_from) + }; + + Self { + events: [start] + .iter() + .chain(self.events.iter().skip_while(|event| event.location() <= trim_from)) + .cloned() + .collect(), + } + } } diff --git a/src/test/ui/generator/drop-control-flow.rs b/src/test/ui/generator/drop-control-flow.rs index 6587e54df6083..6319a29f5b7d0 100644 --- a/src/test/ui/generator/drop-control-flow.rs +++ b/src/test/ui/generator/drop-control-flow.rs @@ -5,7 +5,6 @@ // included in the generator type. #![feature(generators, negative_impls)] - #![allow(unused_assignments, dead_code)] struct Ptr; @@ -13,7 +12,7 @@ impl<'a> Drop for Ptr { fn drop(&mut self) {} } -struct NonSend {} +struct NonSend; impl !Send for NonSend {} fn assert_send(_: T) {} @@ -51,6 +50,29 @@ fn if_let(arg: Option) { }; } +fn init_in_if(arg: bool) { + assert_send(|| { + let mut x = NonSend; + drop(x); + if arg { + x = NonSend; + } else { + yield; + } + }) +} + +fn init_in_match_arm(arg: Option) { + assert_send(|| { + let mut x = NonSend; + drop(x); + match arg { + Some(_) => x = NonSend, + None => yield, + } + }) +} + fn reinit() { let _ = || { let mut arr = [Ptr]; @@ -73,9 +95,27 @@ fn loop_uninit() { }; } +fn nested_loop() { + let _ = || { + let mut arr = [Ptr]; + let mut count = 0; + drop(arr); + while count < 3 { + for _ in 0..3 { + yield; + } + arr = [Ptr]; + count += 1; + } + }; +} + fn main() { one_armed_if(true); if_let(Some(41)); + init_in_if(true); + init_in_match_arm(Some(41)); reinit(); loop_uninit(); + nested_loop(); } From ba7d12731eb23154ea42feecec172bbef73fd865 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Fri, 5 Nov 2021 15:10:33 -0700 Subject: [PATCH 10/35] More tracing and tests --- .../src/check/generator_interior.rs | 32 +++++++++++-------- .../src/check/generator_interior/tests.rs | 14 ++++++++ 2 files changed, 32 insertions(+), 14 deletions(-) create mode 100644 compiler/rustc_typeck/src/check/generator_interior/tests.rs diff --git a/compiler/rustc_typeck/src/check/generator_interior.rs b/compiler/rustc_typeck/src/check/generator_interior.rs index e7794b199e714..3097729301840 100644 --- a/compiler/rustc_typeck/src/check/generator_interior.rs +++ b/compiler/rustc_typeck/src/check/generator_interior.rs @@ -773,7 +773,7 @@ impl DropRangeVisitor<'tcx> { debug!("reinitializing {:?} at {}", hir_id, location); self.drop_range(hir_id).reinit(location) } else { - warn!("reinitializing {:?} is not supported", expr); + debug!("reinitializing {:?} is not supported", expr); } } } @@ -899,6 +899,7 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { reinit = Some(lhs); } ExprKind::Loop(body, ..) => { + // FIXME: we probably need to iterate this to a fixpoint. let body_drop_ranges = self.fork_drop_ranges(); let old_drop_ranges = self.swap_drop_ranges(body_drop_ranges); @@ -1026,8 +1027,8 @@ impl DropRange { } fn is_dropped_at(&self, id: usize) -> bool { - match self.events.iter().try_fold(false, |is_dropped, event| { - if event.location() < id { + let dropped = match self.events.iter().try_fold(false, |is_dropped, event| { + if event.location() <= id { Ok(match event { Event::Drop(_) => true, Event::Reinit(_) => false, @@ -1037,7 +1038,9 @@ impl DropRange { } }) { Ok(is_dropped) | Err(is_dropped) => is_dropped, - } + }; + trace!("is_dropped_at({}): events = {:?}, dropped = {}", id, self.events, dropped); + dropped } fn drop(&mut self, location: usize) { @@ -1052,13 +1055,14 @@ impl DropRange { /// /// After merging, the value will be dead at the end of the range only if it was dead /// at the end of both self and other. + #[tracing::instrument] fn merge_with(&mut self, other: &DropRange, join_point: usize) { let join_event = if self.is_dropped_at(join_point) && other.is_dropped_at(join_point) { Event::Drop(join_point) } else { Event::Reinit(join_point) }; - let mut events: Vec<_> = self + let events: Vec<_> = self .events .iter() .merge([join_event].iter()) @@ -1067,11 +1071,7 @@ impl DropRange { .cloned() .collect(); - events.push(if self.is_dropped_at(join_point) && other.is_dropped_at(join_point) { - Event::Drop(join_point) - } else { - Event::Reinit(join_point) - }); + trace!("events after merging: {:?}", events); self.events = events; } @@ -1080,13 +1080,15 @@ impl DropRange { /// /// Used to model branching control flow. fn fork_at(&self, split_point: usize) -> Self { - Self { + let result = Self { events: vec![if self.is_dropped_at(split_point) { Event::Drop(split_point) } else { Event::Reinit(split_point) }], - } + }; + trace!("forking at {}: {:?}; result = {:?}", split_point, self.events, result); + result } fn trimmed(&self, trim_from: usize) -> Self { @@ -1096,12 +1098,14 @@ impl DropRange { Event::Reinit(trim_from) }; - Self { + let result = Self { events: [start] .iter() .chain(self.events.iter().skip_while(|event| event.location() <= trim_from)) .cloned() .collect(), - } + }; + trace!("trimmed {:?} at {}, got {:?}", self, trim_from, result); + result } } diff --git a/compiler/rustc_typeck/src/check/generator_interior/tests.rs b/compiler/rustc_typeck/src/check/generator_interior/tests.rs new file mode 100644 index 0000000000000..8f973bb94895a --- /dev/null +++ b/compiler/rustc_typeck/src/check/generator_interior/tests.rs @@ -0,0 +1,14 @@ +use super::DropRange; + +#[test] +fn drop_range_uses_last_event() { + let mut range = DropRange::empty(); + range.drop(10); + range.reinit(10); + assert!(!range.is_dropped_at(10)); + + let mut range = DropRange::empty(); + range.reinit(10); + range.drop(10); + assert!(range.is_dropped_at(10)); +} From ff0e8f4ba2e3133a167b138ba1ed69d1708fff16 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Wed, 10 Nov 2021 17:32:04 -0800 Subject: [PATCH 11/35] Revamped DropRange data structure Not currently working. Need to flow drop information. --- Cargo.lock | 2 +- compiler/rustc_typeck/Cargo.toml | 2 +- .../src/check/generator_interior.rs | 388 +++++------------- .../check/generator_interior/drop_ranges.rs | 173 ++++++++ 4 files changed, 273 insertions(+), 292 deletions(-) create mode 100644 compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs diff --git a/Cargo.lock b/Cargo.lock index dfe3db5907a33..b46696dde7cf4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4383,7 +4383,6 @@ dependencies = [ name = "rustc_typeck" version = "0.0.0" dependencies = [ - "itertools 0.9.0", "rustc_arena", "rustc_ast", "rustc_attr", @@ -4396,6 +4395,7 @@ dependencies = [ "rustc_lint", "rustc_macros", "rustc_middle", + "rustc_serialize", "rustc_session", "rustc_span", "rustc_target", diff --git a/compiler/rustc_typeck/Cargo.toml b/compiler/rustc_typeck/Cargo.toml index 1da106d7002ef..3279c331ade18 100644 --- a/compiler/rustc_typeck/Cargo.toml +++ b/compiler/rustc_typeck/Cargo.toml @@ -8,7 +8,6 @@ test = false doctest = false [dependencies] -itertools = "0.9" rustc_arena = { path = "../rustc_arena" } tracing = "0.1" rustc_macros = { path = "../rustc_macros" } @@ -28,3 +27,4 @@ rustc_infer = { path = "../rustc_infer" } rustc_trait_selection = { path = "../rustc_trait_selection" } rustc_ty_utils = { path = "../rustc_ty_utils" } rustc_lint = { path = "../rustc_lint" } +rustc_serialize = { path = "../rustc_serialize" } diff --git a/compiler/rustc_typeck/src/check/generator_interior.rs b/compiler/rustc_typeck/src/check/generator_interior.rs index 3097729301840..33533ab6189f0 100644 --- a/compiler/rustc_typeck/src/check/generator_interior.rs +++ b/compiler/rustc_typeck/src/check/generator_interior.rs @@ -3,13 +3,12 @@ //! is calculated in `rustc_const_eval::transform::generator` and may be a subset of the //! types computed here. -use std::mem; - use crate::expr_use_visitor::{self, ExprUseVisitor}; +use self::drop_ranges::DropRanges; + use super::FnCtxt; use hir::{HirIdMap, Node}; -use itertools::Itertools; use rustc_data_structures::fx::{FxHashSet, FxIndexSet}; use rustc_errors::pluralize; use rustc_hir as hir; @@ -30,6 +29,8 @@ use tracing::debug; #[cfg(test)] mod tests; +mod drop_ranges; + struct InteriorVisitor<'a, 'tcx> { fcx: &'a FnCtxt<'a, 'tcx>, types: FxIndexSet>, @@ -45,7 +46,7 @@ struct InteriorVisitor<'a, 'tcx> { guard_bindings: SmallVec<[SmallVec<[HirId; 4]>; 1]>, guard_bindings_set: HirIdSet, linted_values: HirIdSet, - drop_ranges: HirIdMap, + drop_ranges: DropRanges, } impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> { @@ -85,14 +86,10 @@ impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> { yield_data.expr_and_pat_count, self.expr_count, source_span ); - match self.drop_ranges.get(&hir_id) { - Some(range) - if range.is_dropped_at(yield_data.expr_and_pat_count) => - { - debug!("value is dropped at yield point; not recording"); - return false; - } - _ => (), + if self.drop_ranges.is_dropped_at(hir_id, yield_data.expr_and_pat_count) + { + debug!("value is dropped at yield point; not recording"); + return false; } // If it is a borrowing happening in the guard, @@ -233,25 +230,27 @@ pub fn resolve_interior<'a, 'tcx>( let body = fcx.tcx.hir().body(body_id); let mut visitor = { - let mut drop_range_visitor = DropRangeVisitor { + let mut expr_use_visitor = ExprUseDelegate { hir: fcx.tcx.hir(), consumed_places: <_>::default(), borrowed_places: <_>::default(), - drop_ranges: <_>::default(), - expr_count: 0, }; // Run ExprUseVisitor to find where values are consumed. ExprUseVisitor::new( - &mut drop_range_visitor, + &mut expr_use_visitor, &fcx.infcx, def_id.expect_local(), fcx.param_env, &fcx.typeck_results.borrow(), ) .consume_body(body); + + let mut drop_range_visitor = DropRangeVisitor::from(expr_use_visitor); intravisit::walk_body(&mut drop_range_visitor, body); + drop_range_visitor.drop_ranges.propagate_to_fixpoint(); + InteriorVisitor { fcx, types: FxIndexSet::default(), @@ -673,29 +672,46 @@ fn check_must_not_suspend_def( false } -/// This struct facilitates computing the ranges for which a place is uninitialized. -struct DropRangeVisitor<'tcx> { +struct ExprUseDelegate<'tcx> { hir: Map<'tcx>, /// Maps a HirId to a set of HirIds that are dropped by that node. consumed_places: HirIdMap, borrowed_places: HirIdSet, - drop_ranges: HirIdMap, - expr_count: usize, } -impl DropRangeVisitor<'tcx> { +impl<'tcx> ExprUseDelegate<'tcx> { fn mark_consumed(&mut self, consumer: HirId, target: HirId) { if !self.consumed_places.contains_key(&consumer) { self.consumed_places.insert(consumer, <_>::default()); } self.consumed_places.get_mut(&consumer).map(|places| places.insert(target)); } +} - fn drop_range(&mut self, hir_id: &HirId) -> &mut DropRange { - if !self.drop_ranges.contains_key(hir_id) { - self.drop_ranges.insert(*hir_id, DropRange::empty()); +/// This struct facilitates computing the ranges for which a place is uninitialized. +struct DropRangeVisitor<'tcx> { + hir: Map<'tcx>, + /// Maps a HirId to a set of HirIds that are dropped by that node. + consumed_places: HirIdMap, + borrowed_places: HirIdSet, + drop_ranges: DropRanges, + expr_count: usize, +} + +impl<'tcx> DropRangeVisitor<'tcx> { + fn from(uses: ExprUseDelegate<'tcx>) -> Self { + debug!("consumed_places: {:?}", uses.consumed_places); + let drop_ranges = DropRanges::new( + uses.consumed_places.iter().flat_map(|(_, places)| places.iter().copied()), + &uses.hir, + ); + Self { + hir: uses.hir, + consumed_places: uses.consumed_places, + borrowed_places: uses.borrowed_places, + drop_ranges, + expr_count: 0, } - self.drop_ranges.get_mut(hir_id).unwrap() } fn record_drop(&mut self, hir_id: HirId) { @@ -704,41 +720,10 @@ impl DropRangeVisitor<'tcx> { } else { debug!("marking {:?} as dropped at {}", hir_id, self.expr_count); let count = self.expr_count; - self.drop_range(&hir_id).drop(count); + self.drop_ranges.drop_at(hir_id, count); } } - fn swap_drop_ranges(&mut self, mut other: HirIdMap) -> HirIdMap { - mem::swap(&mut self.drop_ranges, &mut other); - other - } - - fn fork_drop_ranges(&self) -> HirIdMap { - self.drop_ranges.iter().map(|(k, v)| (*k, v.fork_at(self.expr_count))).collect() - } - - fn intersect_drop_ranges(&mut self, drops: HirIdMap) { - drops.into_iter().for_each(|(k, v)| match self.drop_ranges.get_mut(&k) { - Some(ranges) => *ranges = ranges.intersect(&v), - None => { - self.drop_ranges.insert(k, v); - } - }) - } - - fn merge_drop_ranges_at(&mut self, drops: HirIdMap, join_point: usize) { - drops.into_iter().for_each(|(k, v)| { - if !self.drop_ranges.contains_key(&k) { - self.drop_ranges.insert(k, DropRange { events: vec![] }); - } - self.drop_ranges.get_mut(&k).unwrap().merge_with(&v, join_point); - }); - } - - fn merge_drop_ranges(&mut self, drops: HirIdMap) { - self.merge_drop_ranges_at(drops, self.expr_count); - } - /// ExprUseVisitor's consume callback doesn't go deep enough for our purposes in all /// expressions. This method consumes a little deeper into the expression when needed. fn consume_expr(&mut self, expr: &hir::Expr<'_>) { @@ -748,18 +733,7 @@ impl DropRangeVisitor<'tcx> { .get(&expr.hir_id) .map_or(vec![], |places| places.iter().cloned().collect()); for place in places { - self.record_drop(place); - if let Some(Node::Expr(expr)) = self.hir.find(place) { - match expr.kind { - hir::ExprKind::Path(hir::QPath::Resolved( - _, - hir::Path { res: hir::def::Res::Local(hir_id), .. }, - )) => { - self.record_drop(*hir_id); - } - _ => (), - } - } + for_each_consumable(place, self.hir.find(place), |hir_id| self.record_drop(hir_id)); } } @@ -771,13 +745,28 @@ impl DropRangeVisitor<'tcx> { { let location = self.expr_count; debug!("reinitializing {:?} at {}", hir_id, location); - self.drop_range(hir_id).reinit(location) + self.drop_ranges.reinit_at(*hir_id, location); } else { debug!("reinitializing {:?} is not supported", expr); } } } +fn for_each_consumable(place: HirId, node: Option>, mut f: impl FnMut(HirId)) { + f(place); + if let Some(Node::Expr(expr)) = node { + match expr.kind { + hir::ExprKind::Path(hir::QPath::Resolved( + _, + hir::Path { res: hir::def::Res::Local(hir_id), .. }, + )) => { + f(*hir_id); + } + _ => (), + } + } +} + fn place_hir_id(place: &Place<'_>) -> Option { match place.base { PlaceBase::Rvalue | PlaceBase::StaticItem => None, @@ -786,7 +775,7 @@ fn place_hir_id(place: &Place<'_>) -> Option { } } -impl<'tcx> expr_use_visitor::Delegate<'tcx> for DropRangeVisitor<'tcx> { +impl<'tcx> expr_use_visitor::Delegate<'tcx> for ExprUseDelegate<'tcx> { fn consume( &mut self, place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>, @@ -839,58 +828,41 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { let mut reinit = None; match expr.kind { - ExprKind::AssignOp(_op, lhs, rhs) => { - // These operations are weird because their order of evaluation depends on whether - // the operator is overloaded. In a perfect world, we'd just ask the type checker - // whether this is a method call, but we also need to match the expression IDs - // from RegionResolutionVisitor. RegionResolutionVisitor doesn't know the order, - // so it runs both orders and picks the most conservative. We'll mirror that here. - let mut old_count = self.expr_count; - self.visit_expr(lhs); - self.visit_expr(rhs); - - let old_drops = self.swap_drop_ranges(<_>::default()); - std::mem::swap(&mut old_count, &mut self.expr_count); - self.visit_expr(rhs); - self.visit_expr(lhs); - - // We should have visited the same number of expressions in either order. - assert_eq!(old_count, self.expr_count); - - self.intersect_drop_ranges(old_drops); - } + // ExprKind::AssignOp(_op, lhs, rhs) => { + // // These operations are weird because their order of evaluation depends on whether + // // the operator is overloaded. In a perfect world, we'd just ask the type checker + // // whether this is a method call, but we also need to match the expression IDs + // // from RegionResolutionVisitor. RegionResolutionVisitor doesn't know the order, + // // so it runs both orders and picks the most conservative. We'll mirror that here. + // let mut old_count = self.expr_count; + // self.visit_expr(lhs); + // self.visit_expr(rhs); + + // let old_drops = self.swap_drop_ranges(<_>::default()); + // std::mem::swap(&mut old_count, &mut self.expr_count); + // self.visit_expr(rhs); + // self.visit_expr(lhs); + + // // We should have visited the same number of expressions in either order. + // assert_eq!(old_count, self.expr_count); + + // self.intersect_drop_ranges(old_drops); + // } ExprKind::If(test, if_true, if_false) => { self.visit_expr(test); - match if_false { - Some(if_false) => { - let mut true_ranges = self.fork_drop_ranges(); - let mut false_ranges = self.fork_drop_ranges(); + let fork = self.expr_count - 1; - true_ranges = self.swap_drop_ranges(true_ranges); - self.visit_expr(if_true); - true_ranges = self.swap_drop_ranges(true_ranges); + self.drop_ranges.add_control_edge(fork, self.expr_count); + self.visit_expr(if_true); + let true_end = self.expr_count - 1; - false_ranges = - self.swap_drop_ranges(trim_drop_ranges(&false_ranges, self.expr_count)); - self.visit_expr(if_false); - false_ranges = self.swap_drop_ranges(false_ranges); - - self.merge_drop_ranges(true_ranges); - self.merge_drop_ranges(false_ranges); - } - None => { - let mut true_ranges = self.fork_drop_ranges(); - debug!("true branch drop range fork: {:?}", true_ranges); - true_ranges = self.swap_drop_ranges(true_ranges); - self.visit_expr(if_true); - true_ranges = self.swap_drop_ranges(true_ranges); - debug!("true branch computed drop_ranges: {:?}", true_ranges); - debug!("drop ranges before merging: {:?}", self.drop_ranges); - self.merge_drop_ranges(true_ranges); - debug!("drop ranges after merging: {:?}", self.drop_ranges); - } + if let Some(if_false) = if_false { + self.drop_ranges.add_control_edge(fork, self.expr_count); + self.visit_expr(if_false); } + + self.drop_ranges.add_control_edge(true_end, self.expr_count); } ExprKind::Assign(lhs, rhs, _) => { self.visit_expr(lhs); @@ -899,27 +871,18 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { reinit = Some(lhs); } ExprKind::Loop(body, ..) => { - // FIXME: we probably need to iterate this to a fixpoint. - let body_drop_ranges = self.fork_drop_ranges(); - let old_drop_ranges = self.swap_drop_ranges(body_drop_ranges); - - let join_point = self.expr_count; - + let loop_begin = self.expr_count; self.visit_block(body); - - let body_drop_ranges = self.swap_drop_ranges(old_drop_ranges); - self.merge_drop_ranges_at(body_drop_ranges, join_point); + self.drop_ranges.add_control_edge(self.expr_count - 1, loop_begin); } ExprKind::Match(scrutinee, arms, ..) => { self.visit_expr(scrutinee); - let forked_ranges = self.fork_drop_ranges(); + let fork = self.expr_count - 1; let arm_drops = arms .iter() - .map(|Arm { hir_id, pat, body, guard, .. }| { - debug!("match arm {:?} starts at {}", hir_id, self.expr_count); - let old_ranges = self - .swap_drop_ranges(trim_drop_ranges(&forked_ranges, self.expr_count)); + .map(|Arm { pat, body, guard, .. }| { + self.drop_ranges.add_control_edge(fork, self.expr_count); self.visit_pat(pat); match guard { Some(Guard::If(expr)) => self.visit_expr(expr), @@ -930,10 +893,12 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { None => (), } self.visit_expr(body); - self.swap_drop_ranges(old_ranges) + self.expr_count - 1 }) .collect::>(); - arm_drops.into_iter().for_each(|drops| self.merge_drop_ranges(drops)); + arm_drops.into_iter().for_each(|arm_end| { + self.drop_ranges.add_control_edge(arm_end, self.expr_count) + }); } _ => intravisit::walk_expr(self, expr), } @@ -952,160 +917,3 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { self.expr_count += 1; } } - -fn trim_drop_ranges(drop_ranges: &HirIdMap, trim_from: usize) -> HirIdMap { - drop_ranges.iter().map(|(k, v)| (*k, v.trimmed(trim_from))).collect() -} - -#[derive(Clone, Debug, PartialEq, Eq)] -enum Event { - Drop(usize), - Reinit(usize), -} - -impl Event { - fn location(&self) -> usize { - match *self { - Event::Drop(i) | Event::Reinit(i) => i, - } - } -} - -impl PartialOrd for Event { - fn partial_cmp(&self, other: &Self) -> Option { - self.location().partial_cmp(&other.location()) - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -struct DropRange { - events: Vec, -} - -impl DropRange { - fn empty() -> Self { - Self { events: vec![] } - } - - fn intersect(&self, other: &Self) -> Self { - let mut events = vec![]; - self.events - .iter() - .merge_join_by(other.events.iter(), |a, b| a.partial_cmp(b).unwrap()) - .fold((false, false), |(left, right), event| match event { - itertools::EitherOrBoth::Both(_, _) => todo!(), - itertools::EitherOrBoth::Left(e) => match e { - Event::Drop(i) => { - if !left && right { - events.push(Event::Drop(*i)); - } - (true, right) - } - Event::Reinit(i) => { - if left && !right { - events.push(Event::Reinit(*i)); - } - (false, right) - } - }, - itertools::EitherOrBoth::Right(e) => match e { - Event::Drop(i) => { - if left && !right { - events.push(Event::Drop(*i)); - } - (left, true) - } - Event::Reinit(i) => { - if !left && right { - events.push(Event::Reinit(*i)); - } - (left, false) - } - }, - }); - Self { events } - } - - fn is_dropped_at(&self, id: usize) -> bool { - let dropped = match self.events.iter().try_fold(false, |is_dropped, event| { - if event.location() <= id { - Ok(match event { - Event::Drop(_) => true, - Event::Reinit(_) => false, - }) - } else { - Err(is_dropped) - } - }) { - Ok(is_dropped) | Err(is_dropped) => is_dropped, - }; - trace!("is_dropped_at({}): events = {:?}, dropped = {}", id, self.events, dropped); - dropped - } - - fn drop(&mut self, location: usize) { - self.events.push(Event::Drop(location)) - } - - fn reinit(&mut self, location: usize) { - self.events.push(Event::Reinit(location)); - } - - /// Merges another range with this one. Meant to be used at control flow join points. - /// - /// After merging, the value will be dead at the end of the range only if it was dead - /// at the end of both self and other. - #[tracing::instrument] - fn merge_with(&mut self, other: &DropRange, join_point: usize) { - let join_event = if self.is_dropped_at(join_point) && other.is_dropped_at(join_point) { - Event::Drop(join_point) - } else { - Event::Reinit(join_point) - }; - let events: Vec<_> = self - .events - .iter() - .merge([join_event].iter()) - .merge(other.events.iter()) - .dedup() - .cloned() - .collect(); - - trace!("events after merging: {:?}", events); - - self.events = events; - } - - /// Creates a new DropRange from this one at the split point. - /// - /// Used to model branching control flow. - fn fork_at(&self, split_point: usize) -> Self { - let result = Self { - events: vec![if self.is_dropped_at(split_point) { - Event::Drop(split_point) - } else { - Event::Reinit(split_point) - }], - }; - trace!("forking at {}: {:?}; result = {:?}", split_point, self.events, result); - result - } - - fn trimmed(&self, trim_from: usize) -> Self { - let start = if self.is_dropped_at(trim_from) { - Event::Drop(trim_from) - } else { - Event::Reinit(trim_from) - }; - - let result = Self { - events: [start] - .iter() - .chain(self.events.iter().skip_while(|event| event.location() <= trim_from)) - .cloned() - .collect(), - }; - trace!("trimmed {:?} at {}, got {:?}", self, trim_from, result); - result - } -} diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs new file mode 100644 index 0000000000000..dadd9b8d75367 --- /dev/null +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs @@ -0,0 +1,173 @@ +use rustc_hir::{HirId, HirIdMap}; +use rustc_index::bit_set::BitSet; +use rustc_index::vec::IndexVec; +use rustc_middle::hir::map::Map; + +use super::for_each_consumable; + +rustc_index::newtype_index! { + pub struct PostOrderId { + DEBUG_FORMAT = "id({})", + } +} + +rustc_index::newtype_index! { + pub struct HirIdIndex { + DEBUG_FORMAT = "hidx({})", + } +} + +pub struct DropRanges { + hir_id_map: HirIdMap, + nodes: IndexVec, +} + +/// DropRanges keeps track of what values are definitely dropped at each point in the code. +/// +/// Values of interest are defined by the hir_id of their place. Locations in code are identified +/// by their index in the post-order traversal. At its core, DropRanges maps +/// (hir_id, post_order_id) -> bool, where a true value indicates that the value is definitely +/// dropped at the point of the node identified by post_order_id. +impl DropRanges { + pub fn new(hir_ids: impl Iterator, hir: &Map<'_>) -> Self { + let mut hir_id_map = HirIdMap::::default(); + let mut next = <_>::from(0u32); + for hir_id in hir_ids { + for_each_consumable(hir_id, hir.find(hir_id), |hir_id| { + if !hir_id_map.contains_key(&hir_id) { + hir_id_map.insert(hir_id, next); + next = <_>::from(next.index() + 1); + } + }); + } + debug!("hir_id_map: {:?}", hir_id_map); + Self { hir_id_map, nodes: <_>::default() } + } + + fn hidx(&self, hir_id: HirId) -> HirIdIndex { + *self.hir_id_map.get(&hir_id).unwrap() + } + + pub fn is_dropped_at(&mut self, hir_id: HirId, location: usize) -> bool { + self.hir_id_map + .get(&hir_id) + .copied() + .map_or(false, |hir_id| self.node(location.into()).drop_state.contains(hir_id)) + } + + /// Returns the number of values (hir_ids) that are tracked + fn num_values(&self) -> usize { + self.hir_id_map.len() + } + + fn node(&mut self, id: PostOrderId) -> &NodeInfo { + let size = self.num_values(); + self.nodes.ensure_contains_elem(id, || NodeInfo::new(size)); + &self.nodes[id] + } + + fn node_mut(&mut self, id: PostOrderId) -> &mut NodeInfo { + let size = self.num_values(); + self.nodes.ensure_contains_elem(id, || NodeInfo::new(size)); + &mut self.nodes[id] + } + + pub fn add_control_edge(&mut self, from: usize, to: usize) { + self.node_mut(from.into()).successors.push(to.into()); + } + + pub fn drop_at(&mut self, value: HirId, location: usize) { + let value = self.hidx(value); + self.node_mut(location.into()).drops.push(value); + } + + pub fn reinit_at(&mut self, value: HirId, location: usize) { + let value = match self.hir_id_map.get(&value) { + Some(value) => *value, + // If there's no value, this is never consumed and therefore is never dropped. We can + // ignore this. + None => return, + }; + self.node_mut(location.into()).reinits.push(value); + } + + pub fn propagate_to_fixpoint(&mut self) { + while self.propagate() {} + } + + fn propagate(&mut self) -> bool { + let mut visited = BitSet::new_empty(self.nodes.len()); + + self.visit(&mut visited, PostOrderId::from(0usize), PostOrderId::from(0usize), false) + } + + fn visit( + &mut self, + visited: &mut BitSet, + id: PostOrderId, + pred_id: PostOrderId, + mut changed: bool, + ) -> bool { + if visited.contains(id) { + return changed; + } + visited.insert(id); + + changed &= self.nodes[id].merge_with(&self.nodes[pred_id]); + + if self.nodes[id].successors.len() == 0 { + self.visit(visited, PostOrderId::from(id.index() + 1), id, changed) + } else { + self.nodes[id] + .successors + .iter() + .fold(changed, |changed, &succ| self.visit(visited, succ, id, changed)) + } + } +} + +struct NodeInfo { + /// IDs of nodes that can follow this one in the control flow + /// + /// If the vec is empty, then control proceeds to the next node. + successors: Vec, + + /// List of hir_ids that are dropped by this node. + drops: Vec, + + /// List of hir_ids that are reinitialized by this node. + reinits: Vec, + + /// Set of values that are definitely dropped at this point. + drop_state: BitSet, +} + +impl NodeInfo { + fn new(num_values: usize) -> Self { + Self { + successors: vec![], + drops: vec![], + reinits: vec![], + drop_state: BitSet::new_empty(num_values), + } + } + + fn merge_with(&mut self, other: &NodeInfo) -> bool { + let mut changed = false; + for place in &self.drops { + if !self.drop_state.contains(place) && !self.reinits.contains(&place) { + changed = true; + self.drop_state.insert(place); + } + } + + for place in &self.reinits { + if self.drop_state.contains(place) { + changed = true; + self.drop_state.remove(place); + } + } + + changed + } +} From c7afaa1686521b1d812646a4ca7005f408dd5d71 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Wed, 17 Nov 2021 11:39:27 -0800 Subject: [PATCH 12/35] Handle break and continue. Change fixpoint computation to handle unreachable nodes. --- .../src/check/generator_interior.rs | 38 +++-- .../check/generator_interior/drop_ranges.rs | 160 ++++++++++++------ 2 files changed, 134 insertions(+), 64 deletions(-) diff --git a/compiler/rustc_typeck/src/check/generator_interior.rs b/compiler/rustc_typeck/src/check/generator_interior.rs index 33533ab6189f0..1df45b566ce75 100644 --- a/compiler/rustc_typeck/src/check/generator_interior.rs +++ b/compiler/rustc_typeck/src/check/generator_interior.rs @@ -246,7 +246,12 @@ pub fn resolve_interior<'a, 'tcx>( ) .consume_body(body); - let mut drop_range_visitor = DropRangeVisitor::from(expr_use_visitor); + let region_scope_tree = fcx.tcx.region_scope_tree(def_id); + + let mut drop_range_visitor = DropRangeVisitor::from( + expr_use_visitor, + region_scope_tree.body_expr_count(body.id()).unwrap_or(0), + ); intravisit::walk_body(&mut drop_range_visitor, body); drop_range_visitor.drop_ranges.propagate_to_fixpoint(); @@ -254,7 +259,7 @@ pub fn resolve_interior<'a, 'tcx>( InteriorVisitor { fcx, types: FxIndexSet::default(), - region_scope_tree: fcx.tcx.region_scope_tree(def_id), + region_scope_tree, expr_count: 0, kind, prev_unresolved_span: None, @@ -699,11 +704,12 @@ struct DropRangeVisitor<'tcx> { } impl<'tcx> DropRangeVisitor<'tcx> { - fn from(uses: ExprUseDelegate<'tcx>) -> Self { + fn from(uses: ExprUseDelegate<'tcx>, num_exprs: usize) -> Self { debug!("consumed_places: {:?}", uses.consumed_places); let drop_ranges = DropRanges::new( uses.consumed_places.iter().flat_map(|(_, places)| places.iter().copied()), &uses.hir, + num_exprs, ); Self { hir: uses.hir, @@ -851,18 +857,18 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { ExprKind::If(test, if_true, if_false) => { self.visit_expr(test); - let fork = self.expr_count - 1; + let fork = self.expr_count; - self.drop_ranges.add_control_edge(fork, self.expr_count); + self.drop_ranges.add_control_edge(fork, self.expr_count + 1); self.visit_expr(if_true); - let true_end = self.expr_count - 1; + let true_end = self.expr_count; + self.drop_ranges.add_control_edge(fork, self.expr_count + 1); if let Some(if_false) = if_false { - self.drop_ranges.add_control_edge(fork, self.expr_count); self.visit_expr(if_false); } - self.drop_ranges.add_control_edge(true_end, self.expr_count); + self.drop_ranges.add_control_edge(true_end, self.expr_count + 1); } ExprKind::Assign(lhs, rhs, _) => { self.visit_expr(lhs); @@ -873,13 +879,13 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { ExprKind::Loop(body, ..) => { let loop_begin = self.expr_count; self.visit_block(body); - self.drop_ranges.add_control_edge(self.expr_count - 1, loop_begin); + self.drop_ranges.add_control_edge(self.expr_count, loop_begin); } ExprKind::Match(scrutinee, arms, ..) => { self.visit_expr(scrutinee); let fork = self.expr_count - 1; - let arm_drops = arms + let arm_end_ids = arms .iter() .map(|Arm { pat, body, guard, .. }| { self.drop_ranges.add_control_edge(fork, self.expr_count); @@ -893,16 +899,22 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { None => (), } self.visit_expr(body); - self.expr_count - 1 + self.expr_count }) .collect::>(); - arm_drops.into_iter().for_each(|arm_end| { - self.drop_ranges.add_control_edge(arm_end, self.expr_count) + arm_end_ids.into_iter().for_each(|arm_end| { + self.drop_ranges.add_control_edge(arm_end, self.expr_count + 1) }); } + ExprKind::Break(hir::Destination { target_id: Ok(target), .. }, ..) + | ExprKind::Continue(hir::Destination { target_id: Ok(target), .. }, ..) => { + self.drop_ranges.add_control_edge_hir_id(self.expr_count, target); + } + _ => intravisit::walk_expr(self, expr), } + self.drop_ranges.add_node_mapping(expr.hir_id, self.expr_count); self.expr_count += 1; self.consume_expr(expr); if let Some(expr) = reinit { diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs index dadd9b8d75367..504734080b69d 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs @@ -1,3 +1,7 @@ +use std::collections::BTreeMap; +use std::fmt::Debug; +use std::mem::swap; + use rustc_hir::{HirId, HirIdMap}; use rustc_index::bit_set::BitSet; use rustc_index::vec::IndexVec; @@ -20,6 +24,19 @@ rustc_index::newtype_index! { pub struct DropRanges { hir_id_map: HirIdMap, nodes: IndexVec, + deferred_edges: Vec<(usize, HirId)>, + // FIXME: This should only be used for loops and break/continue. + post_order_map: HirIdMap, +} + +impl Debug for DropRanges { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("DropRanges") + .field("hir_id_map", &self.hir_id_map) + .field("post_order_maps", &self.post_order_map) + .field("nodes", &self.nodes.iter_enumerated().collect::>()) + .finish() + } } /// DropRanges keeps track of what values are definitely dropped at each point in the code. @@ -29,7 +46,7 @@ pub struct DropRanges { /// (hir_id, post_order_id) -> bool, where a true value indicates that the value is definitely /// dropped at the point of the node identified by post_order_id. impl DropRanges { - pub fn new(hir_ids: impl Iterator, hir: &Map<'_>) -> Self { + pub fn new(hir_ids: impl Iterator, hir: &Map<'_>, num_exprs: usize) -> Self { let mut hir_id_map = HirIdMap::::default(); let mut next = <_>::from(0u32); for hir_id in hir_ids { @@ -41,7 +58,13 @@ impl DropRanges { }); } debug!("hir_id_map: {:?}", hir_id_map); - Self { hir_id_map, nodes: <_>::default() } + let num_values = hir_id_map.len(); + Self { + hir_id_map, + nodes: IndexVec::from_fn_n(|_| NodeInfo::new(num_values), num_exprs + 1), + deferred_edges: <_>::default(), + post_order_map: <_>::default(), + } } fn hidx(&self, hir_id: HirId) -> HirIdIndex { @@ -52,7 +75,7 @@ impl DropRanges { self.hir_id_map .get(&hir_id) .copied() - .map_or(false, |hir_id| self.node(location.into()).drop_state.contains(hir_id)) + .map_or(false, |hir_id| self.expect_node(location.into()).drop_state.contains(hir_id)) } /// Returns the number of values (hir_ids) that are tracked @@ -60,9 +83,15 @@ impl DropRanges { self.hir_id_map.len() } - fn node(&mut self, id: PostOrderId) -> &NodeInfo { - let size = self.num_values(); - self.nodes.ensure_contains_elem(id, || NodeInfo::new(size)); + /// Adds an entry in the mapping from HirIds to PostOrderIds + /// + /// Needed so that `add_control_edge_hir_id` can work. + pub fn add_node_mapping(&mut self, hir_id: HirId, post_order_id: usize) { + self.post_order_map.insert(hir_id, post_order_id); + } + + /// Returns a reference to the NodeInfo for a node, panicking if it does not exist + fn expect_node(&self, id: PostOrderId) -> &NodeInfo { &self.nodes[id] } @@ -73,9 +102,32 @@ impl DropRanges { } pub fn add_control_edge(&mut self, from: usize, to: usize) { + trace!("adding control edge from {} to {}", from, to); self.node_mut(from.into()).successors.push(to.into()); } + /// Like add_control_edge, but uses a hir_id as the target. + /// + /// This can be used for branches where we do not know the PostOrderId of the target yet, + /// such as when handling `break` or `continue`. + pub fn add_control_edge_hir_id(&mut self, from: usize, to: HirId) { + self.deferred_edges.push((from, to)); + } + + /// Looks up PostOrderId for any control edges added by HirId and adds a proper edge for them. + /// + /// Should be called after visiting the HIR but before solving the control flow, otherwise some + /// edges will be missed. + fn process_deferred_edges(&mut self) { + let mut edges = vec![]; + swap(&mut edges, &mut self.deferred_edges); + edges.into_iter().for_each(|(from, to)| { + let to = *self.post_order_map.get(&to).expect("Expression ID not found"); + trace!("Adding deferred edge from {} to {}", from, to); + self.add_control_edge(from, to) + }); + } + pub fn drop_at(&mut self, value: HirId, location: usize) { let value = self.hidx(value); self.node_mut(location.into()).drops.push(value); @@ -92,40 +144,65 @@ impl DropRanges { } pub fn propagate_to_fixpoint(&mut self) { - while self.propagate() {} - } + trace!("before fixpoint: {:#?}", self); + self.process_deferred_edges(); + let preds = self.compute_predecessors(); + + trace!("predecessors: {:#?}", preds.iter_enumerated().collect::>()); + + let mut propagate = || { + let mut changed = false; + for id in self.nodes.indices() { + let old_state = self.nodes[id].drop_state.clone(); + if preds[id].len() != 0 { + self.nodes[id].drop_state = self.nodes[preds[id][0]].drop_state.clone(); + for pred in &preds[id][1..] { + let state = self.nodes[*pred].drop_state.clone(); + self.nodes[id].drop_state.intersect(&state); + } + } else { + self.nodes[id].drop_state = if id.index() == 0 { + BitSet::new_empty(self.num_values()) + } else { + // If we are not the start node and we have no predecessors, treat + // everything as dropped because there's no way to get here anyway. + BitSet::new_filled(self.num_values()) + }; + }; + for drop in &self.nodes[id].drops.clone() { + self.nodes[id].drop_state.insert(*drop); + } + for reinit in &self.nodes[id].reinits.clone() { + self.nodes[id].drop_state.remove(*reinit); + } - fn propagate(&mut self) -> bool { - let mut visited = BitSet::new_empty(self.nodes.len()); + changed |= old_state != self.nodes[id].drop_state; + } - self.visit(&mut visited, PostOrderId::from(0usize), PostOrderId::from(0usize), false) - } + changed + }; - fn visit( - &mut self, - visited: &mut BitSet, - id: PostOrderId, - pred_id: PostOrderId, - mut changed: bool, - ) -> bool { - if visited.contains(id) { - return changed; - } - visited.insert(id); + while propagate() {} - changed &= self.nodes[id].merge_with(&self.nodes[pred_id]); + trace!("after fixpoint: {:#?}", self); + } - if self.nodes[id].successors.len() == 0 { - self.visit(visited, PostOrderId::from(id.index() + 1), id, changed) - } else { - self.nodes[id] - .successors - .iter() - .fold(changed, |changed, &succ| self.visit(visited, succ, id, changed)) + fn compute_predecessors(&self) -> IndexVec> { + let mut preds = IndexVec::from_fn_n(|_| vec![], self.nodes.len()); + for (id, node) in self.nodes.iter_enumerated() { + if node.successors.len() == 0 && id.index() != self.nodes.len() - 1 { + preds[<_>::from(id.index() + 1)].push(id); + } else { + for succ in &node.successors { + preds[*succ].push(id); + } + } } + preds } } +#[derive(Debug)] struct NodeInfo { /// IDs of nodes that can follow this one in the control flow /// @@ -148,26 +225,7 @@ impl NodeInfo { successors: vec![], drops: vec![], reinits: vec![], - drop_state: BitSet::new_empty(num_values), + drop_state: BitSet::new_filled(num_values), } } - - fn merge_with(&mut self, other: &NodeInfo) -> bool { - let mut changed = false; - for place in &self.drops { - if !self.drop_state.contains(place) && !self.reinits.contains(&place) { - changed = true; - self.drop_state.insert(place); - } - } - - for place in &self.reinits { - if self.drop_state.contains(place) { - changed = true; - self.drop_state.remove(place); - } - } - - changed - } } From b39fb9bb7bfed503e85c44913e0fe57825f380fb Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Wed, 17 Nov 2021 17:53:47 -0800 Subject: [PATCH 13/35] Fix control flow handling in generator_interior All tests pass now! The issue was that we weren't handling all edges correctly, but now they are handled consistently. This includes code to dump a graphviz file for the CFG we built for drop tracking. Also removes old DropRanges tests. --- Cargo.lock | 1 + compiler/rustc_typeck/Cargo.toml | 1 + .../src/check/generator_interior.rs | 12 ++-- .../check/generator_interior/drop_ranges.rs | 72 ++++++++++++++++++- .../src/check/generator_interior/tests.rs | 14 ---- 5 files changed, 78 insertions(+), 22 deletions(-) delete mode 100644 compiler/rustc_typeck/src/check/generator_interior/tests.rs diff --git a/Cargo.lock b/Cargo.lock index b46696dde7cf4..cde44f96ce76f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4388,6 +4388,7 @@ dependencies = [ "rustc_attr", "rustc_data_structures", "rustc_errors", + "rustc_graphviz", "rustc_hir", "rustc_hir_pretty", "rustc_index", diff --git a/compiler/rustc_typeck/Cargo.toml b/compiler/rustc_typeck/Cargo.toml index 3279c331ade18..57930a28a35a1 100644 --- a/compiler/rustc_typeck/Cargo.toml +++ b/compiler/rustc_typeck/Cargo.toml @@ -15,6 +15,7 @@ rustc_middle = { path = "../rustc_middle" } rustc_attr = { path = "../rustc_attr" } rustc_data_structures = { path = "../rustc_data_structures" } rustc_errors = { path = "../rustc_errors" } +rustc_graphviz = { path = "../rustc_graphviz" } rustc_hir = { path = "../rustc_hir" } rustc_hir_pretty = { path = "../rustc_hir_pretty" } rustc_target = { path = "../rustc_target" } diff --git a/compiler/rustc_typeck/src/check/generator_interior.rs b/compiler/rustc_typeck/src/check/generator_interior.rs index 1df45b566ce75..f26ba875c01d0 100644 --- a/compiler/rustc_typeck/src/check/generator_interior.rs +++ b/compiler/rustc_typeck/src/check/generator_interior.rs @@ -26,9 +26,6 @@ use rustc_span::Span; use smallvec::SmallVec; use tracing::debug; -#[cfg(test)] -mod tests; - mod drop_ranges; struct InteriorVisitor<'a, 'tcx> { @@ -255,6 +252,7 @@ pub fn resolve_interior<'a, 'tcx>( intravisit::walk_body(&mut drop_range_visitor, body); drop_range_visitor.drop_ranges.propagate_to_fixpoint(); + // drop_range_visitor.drop_ranges.save_graph("drop_ranges.dot"); InteriorVisitor { fcx, @@ -877,18 +875,18 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { reinit = Some(lhs); } ExprKind::Loop(body, ..) => { - let loop_begin = self.expr_count; + let loop_begin = self.expr_count + 1; self.visit_block(body); self.drop_ranges.add_control_edge(self.expr_count, loop_begin); } ExprKind::Match(scrutinee, arms, ..) => { self.visit_expr(scrutinee); - let fork = self.expr_count - 1; + let fork = self.expr_count; let arm_end_ids = arms .iter() .map(|Arm { pat, body, guard, .. }| { - self.drop_ranges.add_control_edge(fork, self.expr_count); + self.drop_ranges.add_control_edge(fork, self.expr_count + 1); self.visit_pat(pat); match guard { Some(Guard::If(expr)) => self.visit_expr(expr), @@ -914,8 +912,8 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { _ => intravisit::walk_expr(self, expr), } - self.drop_ranges.add_node_mapping(expr.hir_id, self.expr_count); self.expr_count += 1; + self.drop_ranges.add_node_mapping(expr.hir_id, self.expr_count); self.consume_expr(expr); if let Some(expr) = reinit { self.reinit_expr(expr); diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs index 504734080b69d..5fe3e4088385d 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs @@ -2,6 +2,7 @@ use std::collections::BTreeMap; use std::fmt::Debug; use std::mem::swap; +use rustc_graphviz as dot; use rustc_hir::{HirId, HirIdMap}; use rustc_index::bit_set::BitSet; use rustc_index::vec::IndexVec; @@ -182,7 +183,9 @@ impl DropRanges { changed }; - while propagate() {} + while propagate() { + trace!("drop_state changed, re-running propagation"); + } trace!("after fixpoint: {:#?}", self); } @@ -200,6 +203,73 @@ impl DropRanges { } preds } + + // pub fn save_graph(&self, filename: &str) { + // use std::fs::File; + // dot::render(self, &mut File::create(filename).unwrap()).unwrap(); + // } +} + +impl<'a> dot::GraphWalk<'a> for DropRanges { + type Node = PostOrderId; + + type Edge = (PostOrderId, PostOrderId); + + fn nodes(&'a self) -> dot::Nodes<'a, Self::Node> { + self.nodes.iter_enumerated().map(|(i, _)| i).collect() + } + + fn edges(&'a self) -> dot::Edges<'a, Self::Edge> { + self.nodes + .iter_enumerated() + .flat_map(|(i, node)| { + if node.successors.len() == 0 { + vec![(i, PostOrderId::from_usize(i.index() + 1))] + } else { + node.successors.iter().map(move |&s| (i, s)).collect() + } + }) + .collect() + } + + fn source(&'a self, edge: &Self::Edge) -> Self::Node { + edge.0 + } + + fn target(&'a self, edge: &Self::Edge) -> Self::Node { + edge.1 + } +} + +impl<'a> dot::Labeller<'a> for DropRanges { + type Node = PostOrderId; + + type Edge = (PostOrderId, PostOrderId); + + fn graph_id(&'a self) -> dot::Id<'a> { + dot::Id::new("drop_ranges").unwrap() + } + + fn node_id(&'a self, n: &Self::Node) -> dot::Id<'a> { + dot::Id::new(format!("id{}", n.index())).unwrap() + } + + fn node_label(&'a self, n: &Self::Node) -> dot::LabelText<'a> { + dot::LabelText::LabelStr( + format!( + "{:?}, local_id: {}", + n, + self.post_order_map + .iter() + .find(|(_hir_id, &post_order_id)| post_order_id == n.index()) + .map_or("".into(), |(hir_id, _)| format!( + "{}", + hir_id.local_id.index() + )) + ) + .into(), + ) + } } #[derive(Debug)] diff --git a/compiler/rustc_typeck/src/check/generator_interior/tests.rs b/compiler/rustc_typeck/src/check/generator_interior/tests.rs deleted file mode 100644 index 8f973bb94895a..0000000000000 --- a/compiler/rustc_typeck/src/check/generator_interior/tests.rs +++ /dev/null @@ -1,14 +0,0 @@ -use super::DropRange; - -#[test] -fn drop_range_uses_last_event() { - let mut range = DropRange::empty(); - range.drop(10); - range.reinit(10); - assert!(!range.is_dropped_at(10)); - - let mut range = DropRange::empty(); - range.reinit(10); - range.drop(10); - assert!(range.is_dropped_at(10)); -} From 904c2701496143e089e01b3a10ccbe7eaa1bf9e5 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Thu, 18 Nov 2021 17:26:30 -0800 Subject: [PATCH 14/35] More comments and small cleanups --- compiler/rustc_passes/src/region.rs | 1 + .../src/check/generator_interior.rs | 214 +++++++++--------- .../check/generator_interior/drop_ranges.rs | 5 - 3 files changed, 108 insertions(+), 112 deletions(-) diff --git a/compiler/rustc_passes/src/region.rs b/compiler/rustc_passes/src/region.rs index 8b22c46f01ba6..fdf93e5893247 100644 --- a/compiler/rustc_passes/src/region.rs +++ b/compiler/rustc_passes/src/region.rs @@ -255,6 +255,7 @@ fn resolve_expr<'tcx>(visitor: &mut RegionResolutionVisitor<'tcx>, expr: &'tcx h hir::ExprKind::AssignOp(..) | hir::ExprKind::Index(..) | hir::ExprKind::Unary(..) + | hir::ExprKind::Call(..) | hir::ExprKind::MethodCall(..) => { // FIXME(https://github.com/rust-lang/rfcs/issues/811) Nested method calls // diff --git a/compiler/rustc_typeck/src/check/generator_interior.rs b/compiler/rustc_typeck/src/check/generator_interior.rs index f26ba875c01d0..0df56dd2ee815 100644 --- a/compiler/rustc_typeck/src/check/generator_interior.rs +++ b/compiler/rustc_typeck/src/check/generator_interior.rs @@ -182,39 +182,6 @@ impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> { } } } - - fn visit_call( - &mut self, - call_expr: &'tcx Expr<'tcx>, - callee: &'tcx Expr<'tcx>, - args: &'tcx [Expr<'tcx>], - ) { - match &callee.kind { - ExprKind::Path(qpath) => { - let res = self.fcx.typeck_results.borrow().qpath_res(qpath, callee.hir_id); - match res { - // Direct calls never need to keep the callee `ty::FnDef` - // ZST in a temporary, so skip its type, just in case it - // can significantly complicate the generator type. - Res::Def( - DefKind::Fn | DefKind::AssocFn | DefKind::Ctor(_, CtorKind::Fn), - _, - ) => { - // NOTE(eddyb) this assumes a path expression has - // no nested expressions to keep track of. - self.expr_count += 1; - - // Record the rest of the call expression normally. - for arg in args { - self.visit_expr(arg); - } - } - _ => intravisit::walk_expr(self, call_expr), - } - } - _ => intravisit::walk_expr(self, call_expr), - } - } } pub fn resolve_interior<'a, 'tcx>( @@ -252,7 +219,6 @@ pub fn resolve_interior<'a, 'tcx>( intravisit::walk_body(&mut drop_range_visitor, body); drop_range_visitor.drop_ranges.propagate_to_fixpoint(); - // drop_range_visitor.drop_ranges.save_graph("drop_ranges.dot"); InteriorVisitor { fcx, @@ -395,7 +361,31 @@ impl<'a, 'tcx> Visitor<'tcx> for InteriorVisitor<'a, 'tcx> { let mut guard_borrowing_from_pattern = false; match &expr.kind { - ExprKind::Call(callee, args) => self.visit_call(expr, callee, args), + ExprKind::Call(callee, args) => match &callee.kind { + ExprKind::Path(qpath) => { + let res = self.fcx.typeck_results.borrow().qpath_res(qpath, callee.hir_id); + match res { + // Direct calls never need to keep the callee `ty::FnDef` + // ZST in a temporary, so skip its type, just in case it + // can significantly complicate the generator type. + Res::Def( + DefKind::Fn | DefKind::AssocFn | DefKind::Ctor(_, CtorKind::Fn), + _, + ) => { + // NOTE(eddyb) this assumes a path expression has + // no nested expressions to keep track of. + self.expr_count += 1; + + // Record the rest of the call expression normally. + for arg in args.iter() { + self.visit_expr(arg); + } + } + _ => intravisit::walk_expr(self, expr), + } + } + _ => intravisit::walk_expr(self, expr), + }, ExprKind::Path(qpath) => { intravisit::walk_expr(self, expr); let res = self.fcx.typeck_results.borrow().qpath_res(qpath, expr.hir_id); @@ -675,6 +665,26 @@ fn check_must_not_suspend_def( false } +// The following structs and impls are used for drop range analysis. +// +// Drop range analysis finds the portions of the tree where a value is guaranteed to be dropped +// (i.e. moved, uninitialized, etc.). This is used to exclude the types of those values from the +// generator type. See `InteriorVisitor::record` for where the results of this analysis are used. +// +// There are three phases to this analysis: +// 1. Use `ExprUseVisitor` to identify the interesting values that are consumed and borrowed. +// 2. Use `DropRangeVisitor` to find where the interesting values are dropped or reinitialized, +// and also build a control flow graph. +// 3. Use `DropRanges::propagate_to_fixpoint` to flow the dropped/reinitialized information through +// the CFG and find the exact points where we know a value is definitely dropped. +// +// The end result is a data structure that maps the post-order index of each node in the HIR tree +// to a set of values that are known to be dropped at that location. + +/// Works with ExprUseVisitor to find interesting values for the drop range analysis. +/// +/// Interesting values are those that are either dropped or borrowed. For dropped values, we also +/// record the parent expression, which is the point where the drop actually takes place. struct ExprUseDelegate<'tcx> { hir: Map<'tcx>, /// Maps a HirId to a set of HirIds that are dropped by that node. @@ -691,7 +701,65 @@ impl<'tcx> ExprUseDelegate<'tcx> { } } -/// This struct facilitates computing the ranges for which a place is uninitialized. +impl<'tcx> expr_use_visitor::Delegate<'tcx> for ExprUseDelegate<'tcx> { + fn consume( + &mut self, + place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>, + diag_expr_id: hir::HirId, + ) { + let parent = match self.hir.find_parent_node(place_with_id.hir_id) { + Some(parent) => parent, + None => place_with_id.hir_id, + }; + debug!( + "consume {:?}; diag_expr_id={:?}, using parent {:?}", + place_with_id, diag_expr_id, parent + ); + self.mark_consumed(parent, place_with_id.hir_id); + place_hir_id(&place_with_id.place).map(|place| self.mark_consumed(parent, place)); + } + + fn borrow( + &mut self, + place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>, + _diag_expr_id: hir::HirId, + _bk: rustc_middle::ty::BorrowKind, + ) { + place_hir_id(&place_with_id.place).map(|place| self.borrowed_places.insert(place)); + } + + fn mutate( + &mut self, + _assignee_place: &expr_use_visitor::PlaceWithHirId<'tcx>, + _diag_expr_id: hir::HirId, + ) { + } + + fn fake_read( + &mut self, + _place: expr_use_visitor::Place<'tcx>, + _cause: rustc_middle::mir::FakeReadCause, + _diag_expr_id: hir::HirId, + ) { + } +} + +/// Gives the hir_id associated with a place if one exists. This is the hir_id that we want to +/// track for a value in the drop range analysis. +fn place_hir_id(place: &Place<'_>) -> Option { + match place.base { + PlaceBase::Rvalue | PlaceBase::StaticItem => None, + PlaceBase::Local(hir_id) + | PlaceBase::Upvar(ty::UpvarId { var_path: ty::UpvarPath { hir_id }, .. }) => Some(hir_id), + } +} + +/// This struct is used to gather the information for `DropRanges` to determine the regions of the +/// HIR tree for which a value is dropped. +/// +/// We are interested in points where a variables is dropped or initialized, and the control flow +/// of the code. We identify locations in code by their post-order traversal index, so it is +/// important for this traversal to match that in `RegionResolutionVisitor` and `InteriorVisitor`. struct DropRangeVisitor<'tcx> { hir: Map<'tcx>, /// Maps a HirId to a set of HirIds that are dropped by that node. @@ -756,6 +824,9 @@ impl<'tcx> DropRangeVisitor<'tcx> { } } +/// Applies `f` to consumable portion of a HIR node. +/// +/// The `node` parameter should be the result of calling `Map::find(place)`. fn for_each_consumable(place: HirId, node: Option>, mut f: impl FnMut(HirId)) { f(place); if let Some(Node::Expr(expr)) = node { @@ -771,57 +842,6 @@ fn for_each_consumable(place: HirId, node: Option>, mut f: impl FnMut(H } } -fn place_hir_id(place: &Place<'_>) -> Option { - match place.base { - PlaceBase::Rvalue | PlaceBase::StaticItem => None, - PlaceBase::Local(hir_id) - | PlaceBase::Upvar(ty::UpvarId { var_path: ty::UpvarPath { hir_id }, .. }) => Some(hir_id), - } -} - -impl<'tcx> expr_use_visitor::Delegate<'tcx> for ExprUseDelegate<'tcx> { - fn consume( - &mut self, - place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>, - diag_expr_id: hir::HirId, - ) { - let parent = match self.hir.find_parent_node(place_with_id.hir_id) { - Some(parent) => parent, - None => place_with_id.hir_id, - }; - debug!( - "consume {:?}; diag_expr_id={:?}, using parent {:?}", - place_with_id, diag_expr_id, parent - ); - self.mark_consumed(parent, place_with_id.hir_id); - place_hir_id(&place_with_id.place).map(|place| self.mark_consumed(parent, place)); - } - - fn borrow( - &mut self, - place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>, - _diag_expr_id: hir::HirId, - _bk: rustc_middle::ty::BorrowKind, - ) { - place_hir_id(&place_with_id.place).map(|place| self.borrowed_places.insert(place)); - } - - fn mutate( - &mut self, - _assignee_place: &expr_use_visitor::PlaceWithHirId<'tcx>, - _diag_expr_id: hir::HirId, - ) { - } - - fn fake_read( - &mut self, - _place: expr_use_visitor::Place<'tcx>, - _cause: rustc_middle::mir::FakeReadCause, - _diag_expr_id: hir::HirId, - ) { - } -} - impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { type Map = intravisit::ErasedMap<'tcx>; @@ -832,26 +852,6 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { let mut reinit = None; match expr.kind { - // ExprKind::AssignOp(_op, lhs, rhs) => { - // // These operations are weird because their order of evaluation depends on whether - // // the operator is overloaded. In a perfect world, we'd just ask the type checker - // // whether this is a method call, but we also need to match the expression IDs - // // from RegionResolutionVisitor. RegionResolutionVisitor doesn't know the order, - // // so it runs both orders and picks the most conservative. We'll mirror that here. - // let mut old_count = self.expr_count; - // self.visit_expr(lhs); - // self.visit_expr(rhs); - - // let old_drops = self.swap_drop_ranges(<_>::default()); - // std::mem::swap(&mut old_count, &mut self.expr_count); - // self.visit_expr(rhs); - // self.visit_expr(lhs); - - // // We should have visited the same number of expressions in either order. - // assert_eq!(old_count, self.expr_count); - - // self.intersect_drop_ranges(old_drops); - // } ExprKind::If(test, if_true, if_false) => { self.visit_expr(test); diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs index 5fe3e4088385d..d497210b43461 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs @@ -203,11 +203,6 @@ impl DropRanges { } preds } - - // pub fn save_graph(&self, filename: &str) { - // use std::fs::File; - // dot::render(self, &mut File::create(filename).unwrap()).unwrap(); - // } } impl<'a> dot::GraphWalk<'a> for DropRanges { From 5feb4d0106b1a7022f2fd4379a1a5abfc8926d99 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Thu, 18 Nov 2021 18:33:40 -0800 Subject: [PATCH 15/35] Refactor code to keep most drop range analysis in drop_ranges.rs --- .../src/check/generator_interior.rs | 288 +----------------- .../check/generator_interior/drop_ranges.rs | 277 ++++++++++++++++- 2 files changed, 283 insertions(+), 282 deletions(-) diff --git a/compiler/rustc_typeck/src/check/generator_interior.rs b/compiler/rustc_typeck/src/check/generator_interior.rs index 0df56dd2ee815..68269f24e9d33 100644 --- a/compiler/rustc_typeck/src/check/generator_interior.rs +++ b/compiler/rustc_typeck/src/check/generator_interior.rs @@ -3,12 +3,9 @@ //! is calculated in `rustc_const_eval::transform::generator` and may be a subset of the //! types computed here. -use crate::expr_use_visitor::{self, ExprUseVisitor}; - -use self::drop_ranges::DropRanges; - +use self::drop_ranges::{DropRangeVisitor, DropRanges, ExprUseDelegate}; use super::FnCtxt; -use hir::{HirIdMap, Node}; +use crate::expr_use_visitor::ExprUseVisitor; use rustc_data_structures::fx::{FxHashSet, FxIndexSet}; use rustc_errors::pluralize; use rustc_hir as hir; @@ -17,8 +14,6 @@ use rustc_hir::def_id::DefId; use rustc_hir::hir_id::HirIdSet; use rustc_hir::intravisit::{self, Visitor}; use rustc_hir::{Arm, Expr, ExprKind, Guard, HirId, Pat, PatKind}; -use rustc_middle::hir::map::Map; -use rustc_middle::hir::place::{Place, PlaceBase}; use rustc_middle::middle::region::{self, YieldData}; use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_span::symbol::sym; @@ -194,11 +189,7 @@ pub fn resolve_interior<'a, 'tcx>( let body = fcx.tcx.hir().body(body_id); let mut visitor = { - let mut expr_use_visitor = ExprUseDelegate { - hir: fcx.tcx.hir(), - consumed_places: <_>::default(), - borrowed_places: <_>::default(), - }; + let mut expr_use_visitor = ExprUseDelegate::new(fcx.tcx.hir()); // Run ExprUseVisitor to find where values are consumed. ExprUseVisitor::new( @@ -211,14 +202,14 @@ pub fn resolve_interior<'a, 'tcx>( .consume_body(body); let region_scope_tree = fcx.tcx.region_scope_tree(def_id); - - let mut drop_range_visitor = DropRangeVisitor::from( + let mut drop_range_visitor = DropRangeVisitor::from_uses( expr_use_visitor, region_scope_tree.body_expr_count(body.id()).unwrap_or(0), ); intravisit::walk_body(&mut drop_range_visitor, body); - drop_range_visitor.drop_ranges.propagate_to_fixpoint(); + let mut drop_ranges = drop_range_visitor.into_drop_ranges(); + drop_ranges.propagate_to_fixpoint(); InteriorVisitor { fcx, @@ -230,7 +221,7 @@ pub fn resolve_interior<'a, 'tcx>( guard_bindings: <_>::default(), guard_bindings_set: <_>::default(), linted_values: <_>::default(), - drop_ranges: drop_range_visitor.drop_ranges, + drop_ranges: drop_ranges, } }; intravisit::walk_body(&mut visitor, body); @@ -377,7 +368,7 @@ impl<'a, 'tcx> Visitor<'tcx> for InteriorVisitor<'a, 'tcx> { self.expr_count += 1; // Record the rest of the call expression normally. - for arg in args.iter() { + for arg in *args { self.visit_expr(arg); } } @@ -664,266 +655,3 @@ fn check_must_not_suspend_def( } false } - -// The following structs and impls are used for drop range analysis. -// -// Drop range analysis finds the portions of the tree where a value is guaranteed to be dropped -// (i.e. moved, uninitialized, etc.). This is used to exclude the types of those values from the -// generator type. See `InteriorVisitor::record` for where the results of this analysis are used. -// -// There are three phases to this analysis: -// 1. Use `ExprUseVisitor` to identify the interesting values that are consumed and borrowed. -// 2. Use `DropRangeVisitor` to find where the interesting values are dropped or reinitialized, -// and also build a control flow graph. -// 3. Use `DropRanges::propagate_to_fixpoint` to flow the dropped/reinitialized information through -// the CFG and find the exact points where we know a value is definitely dropped. -// -// The end result is a data structure that maps the post-order index of each node in the HIR tree -// to a set of values that are known to be dropped at that location. - -/// Works with ExprUseVisitor to find interesting values for the drop range analysis. -/// -/// Interesting values are those that are either dropped or borrowed. For dropped values, we also -/// record the parent expression, which is the point where the drop actually takes place. -struct ExprUseDelegate<'tcx> { - hir: Map<'tcx>, - /// Maps a HirId to a set of HirIds that are dropped by that node. - consumed_places: HirIdMap, - borrowed_places: HirIdSet, -} - -impl<'tcx> ExprUseDelegate<'tcx> { - fn mark_consumed(&mut self, consumer: HirId, target: HirId) { - if !self.consumed_places.contains_key(&consumer) { - self.consumed_places.insert(consumer, <_>::default()); - } - self.consumed_places.get_mut(&consumer).map(|places| places.insert(target)); - } -} - -impl<'tcx> expr_use_visitor::Delegate<'tcx> for ExprUseDelegate<'tcx> { - fn consume( - &mut self, - place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>, - diag_expr_id: hir::HirId, - ) { - let parent = match self.hir.find_parent_node(place_with_id.hir_id) { - Some(parent) => parent, - None => place_with_id.hir_id, - }; - debug!( - "consume {:?}; diag_expr_id={:?}, using parent {:?}", - place_with_id, diag_expr_id, parent - ); - self.mark_consumed(parent, place_with_id.hir_id); - place_hir_id(&place_with_id.place).map(|place| self.mark_consumed(parent, place)); - } - - fn borrow( - &mut self, - place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>, - _diag_expr_id: hir::HirId, - _bk: rustc_middle::ty::BorrowKind, - ) { - place_hir_id(&place_with_id.place).map(|place| self.borrowed_places.insert(place)); - } - - fn mutate( - &mut self, - _assignee_place: &expr_use_visitor::PlaceWithHirId<'tcx>, - _diag_expr_id: hir::HirId, - ) { - } - - fn fake_read( - &mut self, - _place: expr_use_visitor::Place<'tcx>, - _cause: rustc_middle::mir::FakeReadCause, - _diag_expr_id: hir::HirId, - ) { - } -} - -/// Gives the hir_id associated with a place if one exists. This is the hir_id that we want to -/// track for a value in the drop range analysis. -fn place_hir_id(place: &Place<'_>) -> Option { - match place.base { - PlaceBase::Rvalue | PlaceBase::StaticItem => None, - PlaceBase::Local(hir_id) - | PlaceBase::Upvar(ty::UpvarId { var_path: ty::UpvarPath { hir_id }, .. }) => Some(hir_id), - } -} - -/// This struct is used to gather the information for `DropRanges` to determine the regions of the -/// HIR tree for which a value is dropped. -/// -/// We are interested in points where a variables is dropped or initialized, and the control flow -/// of the code. We identify locations in code by their post-order traversal index, so it is -/// important for this traversal to match that in `RegionResolutionVisitor` and `InteriorVisitor`. -struct DropRangeVisitor<'tcx> { - hir: Map<'tcx>, - /// Maps a HirId to a set of HirIds that are dropped by that node. - consumed_places: HirIdMap, - borrowed_places: HirIdSet, - drop_ranges: DropRanges, - expr_count: usize, -} - -impl<'tcx> DropRangeVisitor<'tcx> { - fn from(uses: ExprUseDelegate<'tcx>, num_exprs: usize) -> Self { - debug!("consumed_places: {:?}", uses.consumed_places); - let drop_ranges = DropRanges::new( - uses.consumed_places.iter().flat_map(|(_, places)| places.iter().copied()), - &uses.hir, - num_exprs, - ); - Self { - hir: uses.hir, - consumed_places: uses.consumed_places, - borrowed_places: uses.borrowed_places, - drop_ranges, - expr_count: 0, - } - } - - fn record_drop(&mut self, hir_id: HirId) { - if self.borrowed_places.contains(&hir_id) { - debug!("not marking {:?} as dropped because it is borrowed at some point", hir_id); - } else { - debug!("marking {:?} as dropped at {}", hir_id, self.expr_count); - let count = self.expr_count; - self.drop_ranges.drop_at(hir_id, count); - } - } - - /// ExprUseVisitor's consume callback doesn't go deep enough for our purposes in all - /// expressions. This method consumes a little deeper into the expression when needed. - fn consume_expr(&mut self, expr: &hir::Expr<'_>) { - debug!("consuming expr {:?}, count={}", expr.hir_id, self.expr_count); - let places = self - .consumed_places - .get(&expr.hir_id) - .map_or(vec![], |places| places.iter().cloned().collect()); - for place in places { - for_each_consumable(place, self.hir.find(place), |hir_id| self.record_drop(hir_id)); - } - } - - fn reinit_expr(&mut self, expr: &hir::Expr<'_>) { - if let ExprKind::Path(hir::QPath::Resolved( - _, - hir::Path { res: hir::def::Res::Local(hir_id), .. }, - )) = expr.kind - { - let location = self.expr_count; - debug!("reinitializing {:?} at {}", hir_id, location); - self.drop_ranges.reinit_at(*hir_id, location); - } else { - debug!("reinitializing {:?} is not supported", expr); - } - } -} - -/// Applies `f` to consumable portion of a HIR node. -/// -/// The `node` parameter should be the result of calling `Map::find(place)`. -fn for_each_consumable(place: HirId, node: Option>, mut f: impl FnMut(HirId)) { - f(place); - if let Some(Node::Expr(expr)) = node { - match expr.kind { - hir::ExprKind::Path(hir::QPath::Resolved( - _, - hir::Path { res: hir::def::Res::Local(hir_id), .. }, - )) => { - f(*hir_id); - } - _ => (), - } - } -} - -impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { - type Map = intravisit::ErasedMap<'tcx>; - - fn nested_visit_map(&mut self) -> NestedVisitorMap { - NestedVisitorMap::None - } - - fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { - let mut reinit = None; - match expr.kind { - ExprKind::If(test, if_true, if_false) => { - self.visit_expr(test); - - let fork = self.expr_count; - - self.drop_ranges.add_control_edge(fork, self.expr_count + 1); - self.visit_expr(if_true); - let true_end = self.expr_count; - - self.drop_ranges.add_control_edge(fork, self.expr_count + 1); - if let Some(if_false) = if_false { - self.visit_expr(if_false); - } - - self.drop_ranges.add_control_edge(true_end, self.expr_count + 1); - } - ExprKind::Assign(lhs, rhs, _) => { - self.visit_expr(lhs); - self.visit_expr(rhs); - - reinit = Some(lhs); - } - ExprKind::Loop(body, ..) => { - let loop_begin = self.expr_count + 1; - self.visit_block(body); - self.drop_ranges.add_control_edge(self.expr_count, loop_begin); - } - ExprKind::Match(scrutinee, arms, ..) => { - self.visit_expr(scrutinee); - - let fork = self.expr_count; - let arm_end_ids = arms - .iter() - .map(|Arm { pat, body, guard, .. }| { - self.drop_ranges.add_control_edge(fork, self.expr_count + 1); - self.visit_pat(pat); - match guard { - Some(Guard::If(expr)) => self.visit_expr(expr), - Some(Guard::IfLet(pat, expr)) => { - self.visit_pat(pat); - self.visit_expr(expr); - } - None => (), - } - self.visit_expr(body); - self.expr_count - }) - .collect::>(); - arm_end_ids.into_iter().for_each(|arm_end| { - self.drop_ranges.add_control_edge(arm_end, self.expr_count + 1) - }); - } - ExprKind::Break(hir::Destination { target_id: Ok(target), .. }, ..) - | ExprKind::Continue(hir::Destination { target_id: Ok(target), .. }, ..) => { - self.drop_ranges.add_control_edge_hir_id(self.expr_count, target); - } - - _ => intravisit::walk_expr(self, expr), - } - - self.expr_count += 1; - self.drop_ranges.add_node_mapping(expr.hir_id, self.expr_count); - self.consume_expr(expr); - if let Some(expr) = reinit { - self.reinit_expr(expr); - } - } - - fn visit_pat(&mut self, pat: &'tcx Pat<'tcx>) { - intravisit::walk_pat(self, pat); - - // Increment expr_count here to match what InteriorVisitor expects. - self.expr_count += 1; - } -} diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs index d497210b43461..708ce82478000 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs @@ -1,14 +1,287 @@ +//! Drop range analysis finds the portions of the tree where a value is guaranteed to be dropped +//! (i.e. moved, uninitialized, etc.). This is used to exclude the types of those values from the +//! generator type. See `InteriorVisitor::record` for where the results of this analysis are used. +//! +//! There are three phases to this analysis: +//! 1. Use `ExprUseVisitor` to identify the interesting values that are consumed and borrowed. +//! 2. Use `DropRangeVisitor` to find where the interesting values are dropped or reinitialized, +//! and also build a control flow graph. +//! 3. Use `DropRanges::propagate_to_fixpoint` to flow the dropped/reinitialized information through +//! the CFG and find the exact points where we know a value is definitely dropped. +//! +//! The end result is a data structure that maps the post-order index of each node in the HIR tree +//! to a set of values that are known to be dropped at that location. + use std::collections::BTreeMap; use std::fmt::Debug; use std::mem::swap; +use hir::intravisit::{self, NestedVisitorMap, Visitor}; +use hir::{Expr, ExprKind, Guard, HirId, HirIdMap, HirIdSet, Node}; use rustc_graphviz as dot; -use rustc_hir::{HirId, HirIdMap}; +use rustc_hir as hir; use rustc_index::bit_set::BitSet; use rustc_index::vec::IndexVec; use rustc_middle::hir::map::Map; +use rustc_middle::hir::place::{Place, PlaceBase}; +use rustc_middle::ty; + +use crate::expr_use_visitor; + +/// Works with ExprUseVisitor to find interesting values for the drop range analysis. +/// +/// Interesting values are those that are either dropped or borrowed. For dropped values, we also +/// record the parent expression, which is the point where the drop actually takes place. +pub struct ExprUseDelegate<'tcx> { + hir: Map<'tcx>, + /// Maps a HirId to a set of HirIds that are dropped by that node. + consumed_places: HirIdMap, + borrowed_places: HirIdSet, +} + +impl<'tcx> ExprUseDelegate<'tcx> { + pub fn new(hir: Map<'tcx>) -> Self { + Self { hir, consumed_places: <_>::default(), borrowed_places: <_>::default() } + } + + fn mark_consumed(&mut self, consumer: HirId, target: HirId) { + if !self.consumed_places.contains_key(&consumer) { + self.consumed_places.insert(consumer, <_>::default()); + } + self.consumed_places.get_mut(&consumer).map(|places| places.insert(target)); + } +} + +impl<'tcx> expr_use_visitor::Delegate<'tcx> for ExprUseDelegate<'tcx> { + fn consume( + &mut self, + place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>, + diag_expr_id: hir::HirId, + ) { + let parent = match self.hir.find_parent_node(place_with_id.hir_id) { + Some(parent) => parent, + None => place_with_id.hir_id, + }; + debug!( + "consume {:?}; diag_expr_id={:?}, using parent {:?}", + place_with_id, diag_expr_id, parent + ); + self.mark_consumed(parent, place_with_id.hir_id); + place_hir_id(&place_with_id.place).map(|place| self.mark_consumed(parent, place)); + } + + fn borrow( + &mut self, + place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>, + _diag_expr_id: hir::HirId, + _bk: rustc_middle::ty::BorrowKind, + ) { + place_hir_id(&place_with_id.place).map(|place| self.borrowed_places.insert(place)); + } + + fn mutate( + &mut self, + _assignee_place: &expr_use_visitor::PlaceWithHirId<'tcx>, + _diag_expr_id: hir::HirId, + ) { + } + + fn fake_read( + &mut self, + _place: expr_use_visitor::Place<'tcx>, + _cause: rustc_middle::mir::FakeReadCause, + _diag_expr_id: hir::HirId, + ) { + } +} + +/// Gives the hir_id associated with a place if one exists. This is the hir_id that we want to +/// track for a value in the drop range analysis. +fn place_hir_id(place: &Place<'_>) -> Option { + match place.base { + PlaceBase::Rvalue | PlaceBase::StaticItem => None, + PlaceBase::Local(hir_id) + | PlaceBase::Upvar(ty::UpvarId { var_path: ty::UpvarPath { hir_id }, .. }) => Some(hir_id), + } +} + +/// This struct is used to gather the information for `DropRanges` to determine the regions of the +/// HIR tree for which a value is dropped. +/// +/// We are interested in points where a variables is dropped or initialized, and the control flow +/// of the code. We identify locations in code by their post-order traversal index, so it is +/// important for this traversal to match that in `RegionResolutionVisitor` and `InteriorVisitor`. +pub struct DropRangeVisitor<'tcx> { + hir: Map<'tcx>, + /// Maps a HirId to a set of HirIds that are dropped by that node. + consumed_places: HirIdMap, + borrowed_places: HirIdSet, + drop_ranges: DropRanges, + expr_count: usize, +} + +impl<'tcx> DropRangeVisitor<'tcx> { + pub fn from_uses(uses: ExprUseDelegate<'tcx>, num_exprs: usize) -> Self { + debug!("consumed_places: {:?}", uses.consumed_places); + let drop_ranges = DropRanges::new( + uses.consumed_places.iter().flat_map(|(_, places)| places.iter().copied()), + &uses.hir, + num_exprs, + ); + Self { + hir: uses.hir, + consumed_places: uses.consumed_places, + borrowed_places: uses.borrowed_places, + drop_ranges, + expr_count: 0, + } + } + + pub fn into_drop_ranges(self) -> DropRanges { + self.drop_ranges + } + + fn record_drop(&mut self, hir_id: HirId) { + if self.borrowed_places.contains(&hir_id) { + debug!("not marking {:?} as dropped because it is borrowed at some point", hir_id); + } else { + debug!("marking {:?} as dropped at {}", hir_id, self.expr_count); + let count = self.expr_count; + self.drop_ranges.drop_at(hir_id, count); + } + } + + /// ExprUseVisitor's consume callback doesn't go deep enough for our purposes in all + /// expressions. This method consumes a little deeper into the expression when needed. + fn consume_expr(&mut self, expr: &hir::Expr<'_>) { + debug!("consuming expr {:?}, count={}", expr.hir_id, self.expr_count); + let places = self + .consumed_places + .get(&expr.hir_id) + .map_or(vec![], |places| places.iter().cloned().collect()); + for place in places { + for_each_consumable(place, self.hir.find(place), |hir_id| self.record_drop(hir_id)); + } + } + + fn reinit_expr(&mut self, expr: &hir::Expr<'_>) { + if let ExprKind::Path(hir::QPath::Resolved( + _, + hir::Path { res: hir::def::Res::Local(hir_id), .. }, + )) = expr.kind + { + let location = self.expr_count; + debug!("reinitializing {:?} at {}", hir_id, location); + self.drop_ranges.reinit_at(*hir_id, location); + } else { + debug!("reinitializing {:?} is not supported", expr); + } + } +} -use super::for_each_consumable; +/// Applies `f` to consumable portion of a HIR node. +/// +/// The `node` parameter should be the result of calling `Map::find(place)`. +fn for_each_consumable(place: HirId, node: Option>, mut f: impl FnMut(HirId)) { + f(place); + if let Some(Node::Expr(expr)) = node { + match expr.kind { + hir::ExprKind::Path(hir::QPath::Resolved( + _, + hir::Path { res: hir::def::Res::Local(hir_id), .. }, + )) => { + f(*hir_id); + } + _ => (), + } + } +} + +impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { + type Map = intravisit::ErasedMap<'tcx>; + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } + + fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { + let mut reinit = None; + match expr.kind { + ExprKind::If(test, if_true, if_false) => { + self.visit_expr(test); + + let fork = self.expr_count; + + self.drop_ranges.add_control_edge(fork, self.expr_count + 1); + self.visit_expr(if_true); + let true_end = self.expr_count; + + self.drop_ranges.add_control_edge(fork, self.expr_count + 1); + if let Some(if_false) = if_false { + self.visit_expr(if_false); + } + + self.drop_ranges.add_control_edge(true_end, self.expr_count + 1); + } + ExprKind::Assign(lhs, rhs, _) => { + self.visit_expr(lhs); + self.visit_expr(rhs); + + reinit = Some(lhs); + } + ExprKind::Loop(body, ..) => { + let loop_begin = self.expr_count + 1; + self.visit_block(body); + self.drop_ranges.add_control_edge(self.expr_count, loop_begin); + } + ExprKind::Match(scrutinee, arms, ..) => { + self.visit_expr(scrutinee); + + let fork = self.expr_count; + let arm_end_ids = arms + .iter() + .map(|hir::Arm { pat, body, guard, .. }| { + self.drop_ranges.add_control_edge(fork, self.expr_count + 1); + self.visit_pat(pat); + match guard { + Some(Guard::If(expr)) => self.visit_expr(expr), + Some(Guard::IfLet(pat, expr)) => { + self.visit_pat(pat); + self.visit_expr(expr); + } + None => (), + } + self.visit_expr(body); + self.expr_count + }) + .collect::>(); + arm_end_ids.into_iter().for_each(|arm_end| { + self.drop_ranges.add_control_edge(arm_end, self.expr_count + 1) + }); + } + ExprKind::Break(hir::Destination { target_id: Ok(target), .. }, ..) + | ExprKind::Continue(hir::Destination { target_id: Ok(target), .. }, ..) => { + self.drop_ranges.add_control_edge_hir_id(self.expr_count, target); + } + + _ => intravisit::walk_expr(self, expr), + } + + self.expr_count += 1; + self.drop_ranges.add_node_mapping(expr.hir_id, self.expr_count); + self.consume_expr(expr); + if let Some(expr) = reinit { + self.reinit_expr(expr); + } + } + + fn visit_pat(&mut self, pat: &'tcx hir::Pat<'tcx>) { + intravisit::walk_pat(self, pat); + + // Increment expr_count here to match what InteriorVisitor expects. + self.expr_count += 1; + } +} rustc_index::newtype_index! { pub struct PostOrderId { From 46760b4e673e95a4775775fe7da028a33d0f50ae Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Mon, 22 Nov 2021 14:53:02 -0800 Subject: [PATCH 16/35] Update async-fn-nonsend.stderr --- .../ui/async-await/async-fn-nonsend.stderr | 35 ++++--------------- 1 file changed, 6 insertions(+), 29 deletions(-) diff --git a/src/test/ui/async-await/async-fn-nonsend.stderr b/src/test/ui/async-await/async-fn-nonsend.stderr index bff282085735c..abba5585c62eb 100644 --- a/src/test/ui/async-await/async-fn-nonsend.stderr +++ b/src/test/ui/async-await/async-fn-nonsend.stderr @@ -1,28 +1,5 @@ error: future cannot be sent between threads safely - --> $DIR/async-fn-nonsend.rs:49:17 - | -LL | assert_send(local_dropped_before_await()); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ future returned by `local_dropped_before_await` is not `Send` - | - = help: within `impl Future`, the trait `Send` is not implemented for `Rc<()>` -note: future is not `Send` as this value is used across an await - --> $DIR/async-fn-nonsend.rs:24:10 - | -LL | let x = non_send(); - | - has type `impl Debug` which is not `Send` -LL | drop(x); -LL | fut().await; - | ^^^^^^ await occurs here, with `x` maybe used later -LL | } - | - `x` is later dropped here -note: required by a bound in `assert_send` - --> $DIR/async-fn-nonsend.rs:46:24 - | -LL | fn assert_send(_: impl Send) {} - | ^^^^ required by this bound in `assert_send` - -error: future cannot be sent between threads safely - --> $DIR/async-fn-nonsend.rs:51:17 + --> $DIR/async-fn-nonsend.rs:50:17 | LL | assert_send(non_send_temporary_in_match()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ future returned by `non_send_temporary_in_match` is not `Send` @@ -32,12 +9,12 @@ note: future is not `Send` as this value is used across an await --> $DIR/async-fn-nonsend.rs:33:25 | LL | match Some(non_send()) { - | ---------- has type `impl Debug` which is not `Send` + | ---------------- has type `Option` which is not `Send` LL | Some(_) => fut().await, - | ^^^^^^ await occurs here, with `non_send()` maybe used later + | ^^^^^^ await occurs here, with `Some(non_send())` maybe used later ... LL | } - | - `non_send()` is later dropped here + | - `Some(non_send())` is later dropped here note: required by a bound in `assert_send` --> $DIR/async-fn-nonsend.rs:46:24 | @@ -45,7 +22,7 @@ LL | fn assert_send(_: impl Send) {} | ^^^^ required by this bound in `assert_send` error: future cannot be sent between threads safely - --> $DIR/async-fn-nonsend.rs:53:17 + --> $DIR/async-fn-nonsend.rs:52:17 | LL | assert_send(non_sync_with_method_call()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ future returned by `non_sync_with_method_call` is not `Send` @@ -68,5 +45,5 @@ note: required by a bound in `assert_send` LL | fn assert_send(_: impl Send) {} | ^^^^ required by this bound in `assert_send` -error: aborting due to 3 previous errors +error: aborting due to 2 previous errors From 006f5471629e309c19084698e3bcb494ce60d808 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Mon, 6 Dec 2021 11:08:00 -0800 Subject: [PATCH 17/35] Add more comments --- .../src/check/generator_interior/drop_ranges.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs index 708ce82478000..eb95c4e0b64a2 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs @@ -34,8 +34,15 @@ use crate::expr_use_visitor; /// record the parent expression, which is the point where the drop actually takes place. pub struct ExprUseDelegate<'tcx> { hir: Map<'tcx>, - /// Maps a HirId to a set of HirIds that are dropped by that node. + /// Records the point at which an expression or local variable is dropped. + /// + /// The key is the hir-id of the expression, and the value is a set or hir-ids for variables + /// or values that are consumed by that expression. + /// + /// Note that this set excludes "partial drops" -- for example, a statement like `drop(x.y)` is + /// not considered a drop of `x`. consumed_places: HirIdMap, + /// A set of hir-ids of values or variables that are borrowed at some point within the body. borrowed_places: HirIdSet, } @@ -114,6 +121,8 @@ fn place_hir_id(place: &Place<'_>) -> Option { pub struct DropRangeVisitor<'tcx> { hir: Map<'tcx>, /// Maps a HirId to a set of HirIds that are dropped by that node. + /// + /// See also the more detailed comment on `ExprUseDelegate.consumed_places`. consumed_places: HirIdMap, borrowed_places: HirIdSet, drop_ranges: DropRanges, From 30e1b1e92e3a685567417f812e3a079a9d51422c Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Mon, 13 Dec 2021 10:47:28 -0800 Subject: [PATCH 18/35] Address code review comments 1. Add test case for partial drops 2. Simplify code in `propagate_to_fixpoint` and remove most clones 3. Clean up PostOrderIndex creation --- .../check/generator_interior/drop_ranges.rs | 44 +++++++++---------- src/test/ui/generator/partial-drop.rs | 21 +++++++++ 2 files changed, 43 insertions(+), 22 deletions(-) create mode 100644 src/test/ui/generator/partial-drop.rs diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs index eb95c4e0b64a2..7b76ff5e02bf7 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs @@ -34,13 +34,13 @@ use crate::expr_use_visitor; /// record the parent expression, which is the point where the drop actually takes place. pub struct ExprUseDelegate<'tcx> { hir: Map<'tcx>, - /// Records the point at which an expression or local variable is dropped. + /// Records the variables/expressions that are dropped by a given expression. /// /// The key is the hir-id of the expression, and the value is a set or hir-ids for variables /// or values that are consumed by that expression. /// /// Note that this set excludes "partial drops" -- for example, a statement like `drop(x.y)` is - /// not considered a drop of `x`. + /// not considered a drop of `x`, although it would be a drop of `x.y`. consumed_places: HirIdMap, /// A set of hir-ids of values or variables that are borrowed at some point within the body. borrowed_places: HirIdSet, @@ -437,29 +437,29 @@ impl DropRanges { let mut changed = false; for id in self.nodes.indices() { let old_state = self.nodes[id].drop_state.clone(); - if preds[id].len() != 0 { - self.nodes[id].drop_state = self.nodes[preds[id][0]].drop_state.clone(); - for pred in &preds[id][1..] { - let state = self.nodes[*pred].drop_state.clone(); - self.nodes[id].drop_state.intersect(&state); - } + let mut new_state = if id.index() == 0 { + BitSet::new_empty(self.num_values()) } else { - self.nodes[id].drop_state = if id.index() == 0 { - BitSet::new_empty(self.num_values()) - } else { - // If we are not the start node and we have no predecessors, treat - // everything as dropped because there's no way to get here anyway. - BitSet::new_filled(self.num_values()) - }; + // If we are not the start node and we have no predecessors, treat + // everything as dropped because there's no way to get here anyway. + BitSet::new_filled(self.num_values()) }; - for drop in &self.nodes[id].drops.clone() { - self.nodes[id].drop_state.insert(*drop); + + for pred in &preds[id] { + let state = &self.nodes[*pred].drop_state; + new_state.intersect(state); + } + + for drop in &self.nodes[id].drops { + new_state.insert(*drop); } - for reinit in &self.nodes[id].reinits.clone() { - self.nodes[id].drop_state.remove(*reinit); + + for reinit in &self.nodes[id].reinits { + new_state.remove(*reinit); } - changed |= old_state != self.nodes[id].drop_state; + changed |= old_state != new_state; + self.nodes[id].drop_state = new_state; } changed @@ -476,7 +476,7 @@ impl DropRanges { let mut preds = IndexVec::from_fn_n(|_| vec![], self.nodes.len()); for (id, node) in self.nodes.iter_enumerated() { if node.successors.len() == 0 && id.index() != self.nodes.len() - 1 { - preds[<_>::from(id.index() + 1)].push(id); + preds[id + 1].push(id); } else { for succ in &node.successors { preds[*succ].push(id); @@ -501,7 +501,7 @@ impl<'a> dot::GraphWalk<'a> for DropRanges { .iter_enumerated() .flat_map(|(i, node)| { if node.successors.len() == 0 { - vec![(i, PostOrderId::from_usize(i.index() + 1))] + vec![(i, i + 1)] } else { node.successors.iter().map(move |&s| (i, s)).collect() } diff --git a/src/test/ui/generator/partial-drop.rs b/src/test/ui/generator/partial-drop.rs new file mode 100644 index 0000000000000..a2f616aa31336 --- /dev/null +++ b/src/test/ui/generator/partial-drop.rs @@ -0,0 +1,21 @@ +// check-pass + +#![feature(negative_impls, generators)] + +struct Foo; +impl !Send for Foo {} + +struct Bar { + foo: Foo, + x: i32, +} + +fn main() { + assert_send(|| { + let guard = Bar { foo: Foo, x: 42 }; + drop(guard.foo); + yield; + }) +} + +fn assert_send(_: T) {} From f5f98d7ee43ae591afffffc34fc2efab48eef785 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Mon, 13 Dec 2021 10:47:28 -0800 Subject: [PATCH 19/35] Refactor drop_ranges Splits drop_ranges into drop_ranges::record_consumed_borrow, drop_ranges::cfg_build, and drop_ranges::cfg_propagate. The top level drop_ranges module has an entry point that does all the coordination of the other three phases, using code original in generator_interior. --- .../src/check/generator_interior.rs | 50 +-- .../check/generator_interior/drop_ranges.rs | 399 ++---------------- .../drop_ranges/cfg_build.rs | 169 ++++++++ .../drop_ranges/cfg_propagate.rs | 67 +++ .../drop_ranges/cfg_visualize.rs | 68 +++ .../drop_ranges/record_consumed_borrow.rs | 112 +++++ 6 files changed, 455 insertions(+), 410 deletions(-) create mode 100644 compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs create mode 100644 compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_propagate.rs create mode 100644 compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_visualize.rs create mode 100644 compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs diff --git a/compiler/rustc_typeck/src/check/generator_interior.rs b/compiler/rustc_typeck/src/check/generator_interior.rs index 68269f24e9d33..56b6dd9a28446 100644 --- a/compiler/rustc_typeck/src/check/generator_interior.rs +++ b/compiler/rustc_typeck/src/check/generator_interior.rs @@ -3,9 +3,8 @@ //! is calculated in `rustc_const_eval::transform::generator` and may be a subset of the //! types computed here. -use self::drop_ranges::{DropRangeVisitor, DropRanges, ExprUseDelegate}; +use self::drop_ranges::DropRanges; use super::FnCtxt; -use crate::expr_use_visitor::ExprUseVisitor; use rustc_data_structures::fx::{FxHashSet, FxIndexSet}; use rustc_errors::pluralize; use rustc_hir as hir; @@ -187,42 +186,17 @@ pub fn resolve_interior<'a, 'tcx>( kind: hir::GeneratorKind, ) { let body = fcx.tcx.hir().body(body_id); - - let mut visitor = { - let mut expr_use_visitor = ExprUseDelegate::new(fcx.tcx.hir()); - - // Run ExprUseVisitor to find where values are consumed. - ExprUseVisitor::new( - &mut expr_use_visitor, - &fcx.infcx, - def_id.expect_local(), - fcx.param_env, - &fcx.typeck_results.borrow(), - ) - .consume_body(body); - - let region_scope_tree = fcx.tcx.region_scope_tree(def_id); - let mut drop_range_visitor = DropRangeVisitor::from_uses( - expr_use_visitor, - region_scope_tree.body_expr_count(body.id()).unwrap_or(0), - ); - intravisit::walk_body(&mut drop_range_visitor, body); - - let mut drop_ranges = drop_range_visitor.into_drop_ranges(); - drop_ranges.propagate_to_fixpoint(); - - InteriorVisitor { - fcx, - types: FxIndexSet::default(), - region_scope_tree, - expr_count: 0, - kind, - prev_unresolved_span: None, - guard_bindings: <_>::default(), - guard_bindings_set: <_>::default(), - linted_values: <_>::default(), - drop_ranges: drop_ranges, - } + let mut visitor = InteriorVisitor { + fcx, + types: FxIndexSet::default(), + region_scope_tree: fcx.tcx.region_scope_tree(def_id), + expr_count: 0, + kind, + prev_unresolved_span: None, + guard_bindings: <_>::default(), + guard_bindings_set: <_>::default(), + linted_values: <_>::default(), + drop_ranges: drop_ranges::compute_drop_ranges(fcx, def_id, body), }; intravisit::walk_body(&mut visitor, body); diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs index 7b76ff5e02bf7..d8bda36b14fe8 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs @@ -12,180 +12,42 @@ //! The end result is a data structure that maps the post-order index of each node in the HIR tree //! to a set of values that are known to be dropped at that location. -use std::collections::BTreeMap; -use std::fmt::Debug; -use std::mem::swap; - -use hir::intravisit::{self, NestedVisitorMap, Visitor}; -use hir::{Expr, ExprKind, Guard, HirId, HirIdMap, HirIdSet, Node}; -use rustc_graphviz as dot; +use self::cfg_build::DropRangeVisitor; +use self::record_consumed_borrow::ExprUseDelegate; +use crate::check::FnCtxt; +use hir::def_id::DefId; +use hir::{Body, HirId, HirIdMap, Node, intravisit}; use rustc_hir as hir; use rustc_index::bit_set::BitSet; use rustc_index::vec::IndexVec; use rustc_middle::hir::map::Map; -use rustc_middle::hir::place::{Place, PlaceBase}; -use rustc_middle::ty; - -use crate::expr_use_visitor; - -/// Works with ExprUseVisitor to find interesting values for the drop range analysis. -/// -/// Interesting values are those that are either dropped or borrowed. For dropped values, we also -/// record the parent expression, which is the point where the drop actually takes place. -pub struct ExprUseDelegate<'tcx> { - hir: Map<'tcx>, - /// Records the variables/expressions that are dropped by a given expression. - /// - /// The key is the hir-id of the expression, and the value is a set or hir-ids for variables - /// or values that are consumed by that expression. - /// - /// Note that this set excludes "partial drops" -- for example, a statement like `drop(x.y)` is - /// not considered a drop of `x`, although it would be a drop of `x.y`. - consumed_places: HirIdMap, - /// A set of hir-ids of values or variables that are borrowed at some point within the body. - borrowed_places: HirIdSet, -} - -impl<'tcx> ExprUseDelegate<'tcx> { - pub fn new(hir: Map<'tcx>) -> Self { - Self { hir, consumed_places: <_>::default(), borrowed_places: <_>::default() } - } - - fn mark_consumed(&mut self, consumer: HirId, target: HirId) { - if !self.consumed_places.contains_key(&consumer) { - self.consumed_places.insert(consumer, <_>::default()); - } - self.consumed_places.get_mut(&consumer).map(|places| places.insert(target)); - } -} - -impl<'tcx> expr_use_visitor::Delegate<'tcx> for ExprUseDelegate<'tcx> { - fn consume( - &mut self, - place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>, - diag_expr_id: hir::HirId, - ) { - let parent = match self.hir.find_parent_node(place_with_id.hir_id) { - Some(parent) => parent, - None => place_with_id.hir_id, - }; - debug!( - "consume {:?}; diag_expr_id={:?}, using parent {:?}", - place_with_id, diag_expr_id, parent - ); - self.mark_consumed(parent, place_with_id.hir_id); - place_hir_id(&place_with_id.place).map(|place| self.mark_consumed(parent, place)); - } - - fn borrow( - &mut self, - place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>, - _diag_expr_id: hir::HirId, - _bk: rustc_middle::ty::BorrowKind, - ) { - place_hir_id(&place_with_id.place).map(|place| self.borrowed_places.insert(place)); - } - - fn mutate( - &mut self, - _assignee_place: &expr_use_visitor::PlaceWithHirId<'tcx>, - _diag_expr_id: hir::HirId, - ) { - } - - fn fake_read( - &mut self, - _place: expr_use_visitor::Place<'tcx>, - _cause: rustc_middle::mir::FakeReadCause, - _diag_expr_id: hir::HirId, - ) { - } -} - -/// Gives the hir_id associated with a place if one exists. This is the hir_id that we want to -/// track for a value in the drop range analysis. -fn place_hir_id(place: &Place<'_>) -> Option { - match place.base { - PlaceBase::Rvalue | PlaceBase::StaticItem => None, - PlaceBase::Local(hir_id) - | PlaceBase::Upvar(ty::UpvarId { var_path: ty::UpvarPath { hir_id }, .. }) => Some(hir_id), - } -} - -/// This struct is used to gather the information for `DropRanges` to determine the regions of the -/// HIR tree for which a value is dropped. -/// -/// We are interested in points where a variables is dropped or initialized, and the control flow -/// of the code. We identify locations in code by their post-order traversal index, so it is -/// important for this traversal to match that in `RegionResolutionVisitor` and `InteriorVisitor`. -pub struct DropRangeVisitor<'tcx> { - hir: Map<'tcx>, - /// Maps a HirId to a set of HirIds that are dropped by that node. - /// - /// See also the more detailed comment on `ExprUseDelegate.consumed_places`. - consumed_places: HirIdMap, - borrowed_places: HirIdSet, - drop_ranges: DropRanges, - expr_count: usize, -} +use std::collections::BTreeMap; +use std::fmt::Debug; +use std::mem::swap; -impl<'tcx> DropRangeVisitor<'tcx> { - pub fn from_uses(uses: ExprUseDelegate<'tcx>, num_exprs: usize) -> Self { - debug!("consumed_places: {:?}", uses.consumed_places); - let drop_ranges = DropRanges::new( - uses.consumed_places.iter().flat_map(|(_, places)| places.iter().copied()), - &uses.hir, - num_exprs, - ); - Self { - hir: uses.hir, - consumed_places: uses.consumed_places, - borrowed_places: uses.borrowed_places, - drop_ranges, - expr_count: 0, - } - } +mod cfg_build; +mod record_consumed_borrow; +mod cfg_propagate; +mod cfg_visualize; - pub fn into_drop_ranges(self) -> DropRanges { - self.drop_ranges - } +pub fn compute_drop_ranges<'a, 'tcx>( + fcx: &'a FnCtxt<'a, 'tcx>, + def_id: DefId, + body: &'tcx Body<'tcx>, +) -> DropRanges { + let mut expr_use_visitor = ExprUseDelegate::new(fcx.tcx.hir()); + expr_use_visitor.consume_body(fcx, def_id, body); - fn record_drop(&mut self, hir_id: HirId) { - if self.borrowed_places.contains(&hir_id) { - debug!("not marking {:?} as dropped because it is borrowed at some point", hir_id); - } else { - debug!("marking {:?} as dropped at {}", hir_id, self.expr_count); - let count = self.expr_count; - self.drop_ranges.drop_at(hir_id, count); - } - } + let mut drop_range_visitor = DropRangeVisitor::from_uses( + expr_use_visitor, + fcx.tcx.region_scope_tree(def_id).body_expr_count(body.id()).unwrap_or(0), + ); + intravisit::walk_body(&mut drop_range_visitor, body); - /// ExprUseVisitor's consume callback doesn't go deep enough for our purposes in all - /// expressions. This method consumes a little deeper into the expression when needed. - fn consume_expr(&mut self, expr: &hir::Expr<'_>) { - debug!("consuming expr {:?}, count={}", expr.hir_id, self.expr_count); - let places = self - .consumed_places - .get(&expr.hir_id) - .map_or(vec![], |places| places.iter().cloned().collect()); - for place in places { - for_each_consumable(place, self.hir.find(place), |hir_id| self.record_drop(hir_id)); - } - } + let mut drop_ranges = drop_range_visitor.into_drop_ranges(); + drop_ranges.propagate_to_fixpoint(); - fn reinit_expr(&mut self, expr: &hir::Expr<'_>) { - if let ExprKind::Path(hir::QPath::Resolved( - _, - hir::Path { res: hir::def::Res::Local(hir_id), .. }, - )) = expr.kind - { - let location = self.expr_count; - debug!("reinitializing {:?} at {}", hir_id, location); - self.drop_ranges.reinit_at(*hir_id, location); - } else { - debug!("reinitializing {:?} is not supported", expr); - } - } + drop_ranges } /// Applies `f` to consumable portion of a HIR node. @@ -206,92 +68,6 @@ fn for_each_consumable(place: HirId, node: Option>, mut f: impl FnMut(H } } -impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { - type Map = intravisit::ErasedMap<'tcx>; - - fn nested_visit_map(&mut self) -> NestedVisitorMap { - NestedVisitorMap::None - } - - fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { - let mut reinit = None; - match expr.kind { - ExprKind::If(test, if_true, if_false) => { - self.visit_expr(test); - - let fork = self.expr_count; - - self.drop_ranges.add_control_edge(fork, self.expr_count + 1); - self.visit_expr(if_true); - let true_end = self.expr_count; - - self.drop_ranges.add_control_edge(fork, self.expr_count + 1); - if let Some(if_false) = if_false { - self.visit_expr(if_false); - } - - self.drop_ranges.add_control_edge(true_end, self.expr_count + 1); - } - ExprKind::Assign(lhs, rhs, _) => { - self.visit_expr(lhs); - self.visit_expr(rhs); - - reinit = Some(lhs); - } - ExprKind::Loop(body, ..) => { - let loop_begin = self.expr_count + 1; - self.visit_block(body); - self.drop_ranges.add_control_edge(self.expr_count, loop_begin); - } - ExprKind::Match(scrutinee, arms, ..) => { - self.visit_expr(scrutinee); - - let fork = self.expr_count; - let arm_end_ids = arms - .iter() - .map(|hir::Arm { pat, body, guard, .. }| { - self.drop_ranges.add_control_edge(fork, self.expr_count + 1); - self.visit_pat(pat); - match guard { - Some(Guard::If(expr)) => self.visit_expr(expr), - Some(Guard::IfLet(pat, expr)) => { - self.visit_pat(pat); - self.visit_expr(expr); - } - None => (), - } - self.visit_expr(body); - self.expr_count - }) - .collect::>(); - arm_end_ids.into_iter().for_each(|arm_end| { - self.drop_ranges.add_control_edge(arm_end, self.expr_count + 1) - }); - } - ExprKind::Break(hir::Destination { target_id: Ok(target), .. }, ..) - | ExprKind::Continue(hir::Destination { target_id: Ok(target), .. }, ..) => { - self.drop_ranges.add_control_edge_hir_id(self.expr_count, target); - } - - _ => intravisit::walk_expr(self, expr), - } - - self.expr_count += 1; - self.drop_ranges.add_node_mapping(expr.hir_id, self.expr_count); - self.consume_expr(expr); - if let Some(expr) = reinit { - self.reinit_expr(expr); - } - } - - fn visit_pat(&mut self, pat: &'tcx hir::Pat<'tcx>) { - intravisit::walk_pat(self, pat); - - // Increment expr_count here to match what InteriorVisitor expects. - self.expr_count += 1; - } -} - rustc_index::newtype_index! { pub struct PostOrderId { DEBUG_FORMAT = "id({})", @@ -426,127 +202,6 @@ impl DropRanges { self.node_mut(location.into()).reinits.push(value); } - pub fn propagate_to_fixpoint(&mut self) { - trace!("before fixpoint: {:#?}", self); - self.process_deferred_edges(); - let preds = self.compute_predecessors(); - - trace!("predecessors: {:#?}", preds.iter_enumerated().collect::>()); - - let mut propagate = || { - let mut changed = false; - for id in self.nodes.indices() { - let old_state = self.nodes[id].drop_state.clone(); - let mut new_state = if id.index() == 0 { - BitSet::new_empty(self.num_values()) - } else { - // If we are not the start node and we have no predecessors, treat - // everything as dropped because there's no way to get here anyway. - BitSet::new_filled(self.num_values()) - }; - - for pred in &preds[id] { - let state = &self.nodes[*pred].drop_state; - new_state.intersect(state); - } - - for drop in &self.nodes[id].drops { - new_state.insert(*drop); - } - - for reinit in &self.nodes[id].reinits { - new_state.remove(*reinit); - } - - changed |= old_state != new_state; - self.nodes[id].drop_state = new_state; - } - - changed - }; - - while propagate() { - trace!("drop_state changed, re-running propagation"); - } - - trace!("after fixpoint: {:#?}", self); - } - - fn compute_predecessors(&self) -> IndexVec> { - let mut preds = IndexVec::from_fn_n(|_| vec![], self.nodes.len()); - for (id, node) in self.nodes.iter_enumerated() { - if node.successors.len() == 0 && id.index() != self.nodes.len() - 1 { - preds[id + 1].push(id); - } else { - for succ in &node.successors { - preds[*succ].push(id); - } - } - } - preds - } -} - -impl<'a> dot::GraphWalk<'a> for DropRanges { - type Node = PostOrderId; - - type Edge = (PostOrderId, PostOrderId); - - fn nodes(&'a self) -> dot::Nodes<'a, Self::Node> { - self.nodes.iter_enumerated().map(|(i, _)| i).collect() - } - - fn edges(&'a self) -> dot::Edges<'a, Self::Edge> { - self.nodes - .iter_enumerated() - .flat_map(|(i, node)| { - if node.successors.len() == 0 { - vec![(i, i + 1)] - } else { - node.successors.iter().map(move |&s| (i, s)).collect() - } - }) - .collect() - } - - fn source(&'a self, edge: &Self::Edge) -> Self::Node { - edge.0 - } - - fn target(&'a self, edge: &Self::Edge) -> Self::Node { - edge.1 - } -} - -impl<'a> dot::Labeller<'a> for DropRanges { - type Node = PostOrderId; - - type Edge = (PostOrderId, PostOrderId); - - fn graph_id(&'a self) -> dot::Id<'a> { - dot::Id::new("drop_ranges").unwrap() - } - - fn node_id(&'a self, n: &Self::Node) -> dot::Id<'a> { - dot::Id::new(format!("id{}", n.index())).unwrap() - } - - fn node_label(&'a self, n: &Self::Node) -> dot::LabelText<'a> { - dot::LabelText::LabelStr( - format!( - "{:?}, local_id: {}", - n, - self.post_order_map - .iter() - .find(|(_hir_id, &post_order_id)| post_order_id == n.index()) - .map_or("".into(), |(hir_id, _)| format!( - "{}", - hir_id.local_id.index() - )) - ) - .into(), - ) - } } #[derive(Debug)] diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs new file mode 100644 index 0000000000000..594054fde9ae7 --- /dev/null +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs @@ -0,0 +1,169 @@ +use super::{for_each_consumable, record_consumed_borrow::ExprUseDelegate, DropRanges}; +use hir::{ + intravisit::{self, NestedVisitorMap, Visitor}, + Expr, ExprKind, Guard, HirId, HirIdMap, HirIdSet, +}; +use rustc_hir as hir; +use rustc_middle::hir::map::Map; + +/// This struct is used to gather the information for `DropRanges` to determine the regions of the +/// HIR tree for which a value is dropped. +/// +/// We are interested in points where a variables is dropped or initialized, and the control flow +/// of the code. We identify locations in code by their post-order traversal index, so it is +/// important for this traversal to match that in `RegionResolutionVisitor` and `InteriorVisitor`. +pub struct DropRangeVisitor<'tcx> { + hir: Map<'tcx>, + /// Maps a HirId to a set of HirIds that are dropped by that node. + /// + /// See also the more detailed comment on `ExprUseDelegate.consumed_places`. + consumed_places: HirIdMap, + borrowed_places: HirIdSet, + drop_ranges: DropRanges, + expr_count: usize, +} + +impl<'tcx> DropRangeVisitor<'tcx> { + pub fn from_uses(uses: ExprUseDelegate<'tcx>, num_exprs: usize) -> Self { + debug!("consumed_places: {:?}", uses.consumed_places); + let drop_ranges = DropRanges::new( + uses.consumed_places.iter().flat_map(|(_, places)| places.iter().copied()), + &uses.hir, + num_exprs, + ); + Self { + hir: uses.hir, + consumed_places: uses.consumed_places, + borrowed_places: uses.borrowed_places, + drop_ranges, + expr_count: 0, + } + } + + pub fn into_drop_ranges(self) -> DropRanges { + self.drop_ranges + } + + fn record_drop(&mut self, hir_id: HirId) { + if self.borrowed_places.contains(&hir_id) { + debug!("not marking {:?} as dropped because it is borrowed at some point", hir_id); + } else { + debug!("marking {:?} as dropped at {}", hir_id, self.expr_count); + let count = self.expr_count; + self.drop_ranges.drop_at(hir_id, count); + } + } + + /// ExprUseVisitor's consume callback doesn't go deep enough for our purposes in all + /// expressions. This method consumes a little deeper into the expression when needed. + fn consume_expr(&mut self, expr: &hir::Expr<'_>) { + debug!("consuming expr {:?}, count={}", expr.hir_id, self.expr_count); + let places = self + .consumed_places + .get(&expr.hir_id) + .map_or(vec![], |places| places.iter().cloned().collect()); + for place in places { + for_each_consumable(place, self.hir.find(place), |hir_id| self.record_drop(hir_id)); + } + } + + fn reinit_expr(&mut self, expr: &hir::Expr<'_>) { + if let ExprKind::Path(hir::QPath::Resolved( + _, + hir::Path { res: hir::def::Res::Local(hir_id), .. }, + )) = expr.kind + { + let location = self.expr_count; + debug!("reinitializing {:?} at {}", hir_id, location); + self.drop_ranges.reinit_at(*hir_id, location); + } else { + debug!("reinitializing {:?} is not supported", expr); + } + } +} + +impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { + type Map = intravisit::ErasedMap<'tcx>; + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } + + fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { + let mut reinit = None; + match expr.kind { + ExprKind::If(test, if_true, if_false) => { + self.visit_expr(test); + + let fork = self.expr_count; + + self.drop_ranges.add_control_edge(fork, self.expr_count + 1); + self.visit_expr(if_true); + let true_end = self.expr_count; + + self.drop_ranges.add_control_edge(fork, self.expr_count + 1); + if let Some(if_false) = if_false { + self.visit_expr(if_false); + } + + self.drop_ranges.add_control_edge(true_end, self.expr_count + 1); + } + ExprKind::Assign(lhs, rhs, _) => { + self.visit_expr(lhs); + self.visit_expr(rhs); + + reinit = Some(lhs); + } + ExprKind::Loop(body, ..) => { + let loop_begin = self.expr_count + 1; + self.visit_block(body); + self.drop_ranges.add_control_edge(self.expr_count, loop_begin); + } + ExprKind::Match(scrutinee, arms, ..) => { + self.visit_expr(scrutinee); + + let fork = self.expr_count; + let arm_end_ids = arms + .iter() + .map(|hir::Arm { pat, body, guard, .. }| { + self.drop_ranges.add_control_edge(fork, self.expr_count + 1); + self.visit_pat(pat); + match guard { + Some(Guard::If(expr)) => self.visit_expr(expr), + Some(Guard::IfLet(pat, expr)) => { + self.visit_pat(pat); + self.visit_expr(expr); + } + None => (), + } + self.visit_expr(body); + self.expr_count + }) + .collect::>(); + arm_end_ids.into_iter().for_each(|arm_end| { + self.drop_ranges.add_control_edge(arm_end, self.expr_count + 1) + }); + } + ExprKind::Break(hir::Destination { target_id: Ok(target), .. }, ..) + | ExprKind::Continue(hir::Destination { target_id: Ok(target), .. }, ..) => { + self.drop_ranges.add_control_edge_hir_id(self.expr_count, target); + } + + _ => intravisit::walk_expr(self, expr), + } + + self.expr_count += 1; + self.drop_ranges.add_node_mapping(expr.hir_id, self.expr_count); + self.consume_expr(expr); + if let Some(expr) = reinit { + self.reinit_expr(expr); + } + } + + fn visit_pat(&mut self, pat: &'tcx hir::Pat<'tcx>) { + intravisit::walk_pat(self, pat); + + // Increment expr_count here to match what InteriorVisitor expects. + self.expr_count += 1; + } +} diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_propagate.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_propagate.rs new file mode 100644 index 0000000000000..ea7b9106b9a80 --- /dev/null +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_propagate.rs @@ -0,0 +1,67 @@ +use std::collections::BTreeMap; + +use rustc_index::{bit_set::BitSet, vec::IndexVec}; + +use super::{DropRanges, PostOrderId}; + +impl DropRanges { + pub fn propagate_to_fixpoint(&mut self) { + trace!("before fixpoint: {:#?}", self); + self.process_deferred_edges(); + let preds = self.compute_predecessors(); + + trace!("predecessors: {:#?}", preds.iter_enumerated().collect::>()); + + let mut propagate = || { + let mut changed = false; + for id in self.nodes.indices() { + let old_state = self.nodes[id].drop_state.clone(); + let mut new_state = if id.index() == 0 { + BitSet::new_empty(self.num_values()) + } else { + // If we are not the start node and we have no predecessors, treat + // everything as dropped because there's no way to get here anyway. + BitSet::new_filled(self.num_values()) + }; + + for pred in &preds[id] { + let state = &self.nodes[*pred].drop_state; + new_state.intersect(state); + } + + for drop in &self.nodes[id].drops { + new_state.insert(*drop); + } + + for reinit in &self.nodes[id].reinits { + new_state.remove(*reinit); + } + + changed |= old_state != new_state; + self.nodes[id].drop_state = new_state; + } + + changed + }; + + while propagate() { + trace!("drop_state changed, re-running propagation"); + } + + trace!("after fixpoint: {:#?}", self); + } + + fn compute_predecessors(&self) -> IndexVec> { + let mut preds = IndexVec::from_fn_n(|_| vec![], self.nodes.len()); + for (id, node) in self.nodes.iter_enumerated() { + if node.successors.len() == 0 && id.index() != self.nodes.len() - 1 { + preds[id + 1].push(id); + } else { + for succ in &node.successors { + preds[*succ].push(id); + } + } + } + preds + } +} diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_visualize.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_visualize.rs new file mode 100644 index 0000000000000..ebbbec1c472b6 --- /dev/null +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_visualize.rs @@ -0,0 +1,68 @@ +//! Implementation of GraphWalk for DropRanges so we can visualize the control +//! flow graph when needed for debugging. + +use rustc_graphviz as dot; + +use super::{DropRanges, PostOrderId}; + +impl<'a> dot::GraphWalk<'a> for DropRanges { + type Node = PostOrderId; + + type Edge = (PostOrderId, PostOrderId); + + fn nodes(&'a self) -> dot::Nodes<'a, Self::Node> { + self.nodes.iter_enumerated().map(|(i, _)| i).collect() + } + + fn edges(&'a self) -> dot::Edges<'a, Self::Edge> { + self.nodes + .iter_enumerated() + .flat_map(|(i, node)| { + if node.successors.len() == 0 { + vec![(i, i + 1)] + } else { + node.successors.iter().map(move |&s| (i, s)).collect() + } + }) + .collect() + } + + fn source(&'a self, edge: &Self::Edge) -> Self::Node { + edge.0 + } + + fn target(&'a self, edge: &Self::Edge) -> Self::Node { + edge.1 + } +} + +impl<'a> dot::Labeller<'a> for DropRanges { + type Node = PostOrderId; + + type Edge = (PostOrderId, PostOrderId); + + fn graph_id(&'a self) -> dot::Id<'a> { + dot::Id::new("drop_ranges").unwrap() + } + + fn node_id(&'a self, n: &Self::Node) -> dot::Id<'a> { + dot::Id::new(format!("id{}", n.index())).unwrap() + } + + fn node_label(&'a self, n: &Self::Node) -> dot::LabelText<'a> { + dot::LabelText::LabelStr( + format!( + "{:?}, local_id: {}", + n, + self.post_order_map + .iter() + .find(|(_hir_id, &post_order_id)| post_order_id == n.index()) + .map_or("".into(), |(hir_id, _)| format!( + "{}", + hir_id.local_id.index() + )) + ) + .into(), + ) + } +} diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs new file mode 100644 index 0000000000000..93bb58cd8a099 --- /dev/null +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs @@ -0,0 +1,112 @@ +use crate::{ + check::FnCtxt, + expr_use_visitor::{self, ExprUseVisitor}, +}; +use hir::{HirId, HirIdMap, HirIdSet, Body, def_id::DefId}; +use rustc_hir as hir; +use rustc_middle::hir::{ + map::Map, + place::{Place, PlaceBase}, +}; +use rustc_middle::ty; + +/// Works with ExprUseVisitor to find interesting values for the drop range analysis. +/// +/// Interesting values are those that are either dropped or borrowed. For dropped values, we also +/// record the parent expression, which is the point where the drop actually takes place. +pub struct ExprUseDelegate<'tcx> { + pub(super) hir: Map<'tcx>, + /// Records the variables/expressions that are dropped by a given expression. + /// + /// The key is the hir-id of the expression, and the value is a set or hir-ids for variables + /// or values that are consumed by that expression. + /// + /// Note that this set excludes "partial drops" -- for example, a statement like `drop(x.y)` is + /// not considered a drop of `x`, although it would be a drop of `x.y`. + pub(super) consumed_places: HirIdMap, + /// A set of hir-ids of values or variables that are borrowed at some point within the body. + pub(super) borrowed_places: HirIdSet, +} + +impl<'tcx> ExprUseDelegate<'tcx> { + pub fn new(hir: Map<'tcx>) -> Self { + Self { hir, consumed_places: <_>::default(), borrowed_places: <_>::default() } + } + + pub fn consume_body( + &mut self, + fcx: &'_ FnCtxt<'_, 'tcx>, + def_id: DefId, + body: &'tcx Body<'tcx>, + ) { + // Run ExprUseVisitor to find where values are consumed. + ExprUseVisitor::new( + self, + &fcx.infcx, + def_id.expect_local(), + fcx.param_env, + &fcx.typeck_results.borrow(), + ) + .consume_body(body); + } + + fn mark_consumed(&mut self, consumer: HirId, target: HirId) { + if !self.consumed_places.contains_key(&consumer) { + self.consumed_places.insert(consumer, <_>::default()); + } + self.consumed_places.get_mut(&consumer).map(|places| places.insert(target)); + } +} + +impl<'tcx> expr_use_visitor::Delegate<'tcx> for ExprUseDelegate<'tcx> { + fn consume( + &mut self, + place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>, + diag_expr_id: HirId, + ) { + let parent = match self.hir.find_parent_node(place_with_id.hir_id) { + Some(parent) => parent, + None => place_with_id.hir_id, + }; + debug!( + "consume {:?}; diag_expr_id={:?}, using parent {:?}", + place_with_id, diag_expr_id, parent + ); + self.mark_consumed(parent, place_with_id.hir_id); + place_hir_id(&place_with_id.place).map(|place| self.mark_consumed(parent, place)); + } + + fn borrow( + &mut self, + place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>, + _diag_expr_id: HirId, + _bk: rustc_middle::ty::BorrowKind, + ) { + place_hir_id(&place_with_id.place).map(|place| self.borrowed_places.insert(place)); + } + + fn mutate( + &mut self, + _assignee_place: &expr_use_visitor::PlaceWithHirId<'tcx>, + _diag_expr_id: HirId, + ) { + } + + fn fake_read( + &mut self, + _place: expr_use_visitor::Place<'tcx>, + _cause: rustc_middle::mir::FakeReadCause, + _diag_expr_id: HirId, + ) { + } +} + +/// Gives the hir_id associated with a place if one exists. This is the hir_id that we want to +/// track for a value in the drop range analysis. +fn place_hir_id(place: &Place<'_>) -> Option { + match place.base { + PlaceBase::Rvalue | PlaceBase::StaticItem => None, + PlaceBase::Local(hir_id) + | PlaceBase::Upvar(ty::UpvarId { var_path: ty::UpvarPath { hir_id }, .. }) => Some(hir_id), + } +} From 9347bf498a9456ab14ec9d9efee25451e80a8642 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Mon, 13 Dec 2021 15:01:26 -0800 Subject: [PATCH 20/35] Additional cleanup This cleans up the refactoring from the previous patch and cleans things up a bit. Each module has a clear entry point and everything else is private. --- .../check/generator_interior/drop_ranges.rs | 94 ++------------- .../drop_ranges/cfg_build.rs | 114 ++++++++++++++---- .../drop_ranges/cfg_propagate.rs | 21 +++- .../drop_ranges/record_consumed_borrow.rs | 52 +++++--- 4 files changed, 151 insertions(+), 130 deletions(-) diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs index d8bda36b14fe8..b200320b8d3b7 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs @@ -12,39 +12,33 @@ //! The end result is a data structure that maps the post-order index of each node in the HIR tree //! to a set of values that are known to be dropped at that location. -use self::cfg_build::DropRangeVisitor; -use self::record_consumed_borrow::ExprUseDelegate; +use self::cfg_build::build_control_flow_graph; +use self::record_consumed_borrow::find_consumed_and_borrowed; use crate::check::FnCtxt; use hir::def_id::DefId; -use hir::{Body, HirId, HirIdMap, Node, intravisit}; +use hir::{Body, HirId, HirIdMap, Node}; use rustc_hir as hir; use rustc_index::bit_set::BitSet; use rustc_index::vec::IndexVec; -use rustc_middle::hir::map::Map; use std::collections::BTreeMap; use std::fmt::Debug; -use std::mem::swap; mod cfg_build; -mod record_consumed_borrow; mod cfg_propagate; mod cfg_visualize; +mod record_consumed_borrow; pub fn compute_drop_ranges<'a, 'tcx>( fcx: &'a FnCtxt<'a, 'tcx>, def_id: DefId, body: &'tcx Body<'tcx>, ) -> DropRanges { - let mut expr_use_visitor = ExprUseDelegate::new(fcx.tcx.hir()); - expr_use_visitor.consume_body(fcx, def_id, body); + let consumed_borrowed_places = find_consumed_and_borrowed(fcx, def_id, body); - let mut drop_range_visitor = DropRangeVisitor::from_uses( - expr_use_visitor, - fcx.tcx.region_scope_tree(def_id).body_expr_count(body.id()).unwrap_or(0), - ); - intravisit::walk_body(&mut drop_range_visitor, body); + let num_exprs = fcx.tcx.region_scope_tree(def_id).body_expr_count(body.id()).unwrap_or(0); + let mut drop_ranges = + build_control_flow_graph(fcx.tcx.hir(), consumed_borrowed_places, body, num_exprs); - let mut drop_ranges = drop_range_visitor.into_drop_ranges(); drop_ranges.propagate_to_fixpoint(); drop_ranges @@ -105,31 +99,6 @@ impl Debug for DropRanges { /// (hir_id, post_order_id) -> bool, where a true value indicates that the value is definitely /// dropped at the point of the node identified by post_order_id. impl DropRanges { - pub fn new(hir_ids: impl Iterator, hir: &Map<'_>, num_exprs: usize) -> Self { - let mut hir_id_map = HirIdMap::::default(); - let mut next = <_>::from(0u32); - for hir_id in hir_ids { - for_each_consumable(hir_id, hir.find(hir_id), |hir_id| { - if !hir_id_map.contains_key(&hir_id) { - hir_id_map.insert(hir_id, next); - next = <_>::from(next.index() + 1); - } - }); - } - debug!("hir_id_map: {:?}", hir_id_map); - let num_values = hir_id_map.len(); - Self { - hir_id_map, - nodes: IndexVec::from_fn_n(|_| NodeInfo::new(num_values), num_exprs + 1), - deferred_edges: <_>::default(), - post_order_map: <_>::default(), - } - } - - fn hidx(&self, hir_id: HirId) -> HirIdIndex { - *self.hir_id_map.get(&hir_id).unwrap() - } - pub fn is_dropped_at(&mut self, hir_id: HirId, location: usize) -> bool { self.hir_id_map .get(&hir_id) @@ -142,13 +111,6 @@ impl DropRanges { self.hir_id_map.len() } - /// Adds an entry in the mapping from HirIds to PostOrderIds - /// - /// Needed so that `add_control_edge_hir_id` can work. - pub fn add_node_mapping(&mut self, hir_id: HirId, post_order_id: usize) { - self.post_order_map.insert(hir_id, post_order_id); - } - /// Returns a reference to the NodeInfo for a node, panicking if it does not exist fn expect_node(&self, id: PostOrderId) -> &NodeInfo { &self.nodes[id] @@ -160,48 +122,10 @@ impl DropRanges { &mut self.nodes[id] } - pub fn add_control_edge(&mut self, from: usize, to: usize) { + fn add_control_edge(&mut self, from: usize, to: usize) { trace!("adding control edge from {} to {}", from, to); self.node_mut(from.into()).successors.push(to.into()); } - - /// Like add_control_edge, but uses a hir_id as the target. - /// - /// This can be used for branches where we do not know the PostOrderId of the target yet, - /// such as when handling `break` or `continue`. - pub fn add_control_edge_hir_id(&mut self, from: usize, to: HirId) { - self.deferred_edges.push((from, to)); - } - - /// Looks up PostOrderId for any control edges added by HirId and adds a proper edge for them. - /// - /// Should be called after visiting the HIR but before solving the control flow, otherwise some - /// edges will be missed. - fn process_deferred_edges(&mut self) { - let mut edges = vec![]; - swap(&mut edges, &mut self.deferred_edges); - edges.into_iter().for_each(|(from, to)| { - let to = *self.post_order_map.get(&to).expect("Expression ID not found"); - trace!("Adding deferred edge from {} to {}", from, to); - self.add_control_edge(from, to) - }); - } - - pub fn drop_at(&mut self, value: HirId, location: usize) { - let value = self.hidx(value); - self.node_mut(location.into()).drops.push(value); - } - - pub fn reinit_at(&mut self, value: HirId, location: usize) { - let value = match self.hir_id_map.get(&value) { - Some(value) => *value, - // If there's no value, this is never consumed and therefore is never dropped. We can - // ignore this. - None => return, - }; - self.node_mut(location.into()).reinits.push(value); - } - } #[derive(Debug)] diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs index 594054fde9ae7..e1f1b44283bbe 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs @@ -1,51 +1,57 @@ -use super::{for_each_consumable, record_consumed_borrow::ExprUseDelegate, DropRanges}; +use super::{ + for_each_consumable, record_consumed_borrow::ConsumedAndBorrowedPlaces, DropRanges, HirIdIndex, + NodeInfo, +}; use hir::{ intravisit::{self, NestedVisitorMap, Visitor}, - Expr, ExprKind, Guard, HirId, HirIdMap, HirIdSet, + Body, Expr, ExprKind, Guard, HirId, HirIdMap, }; use rustc_hir as hir; +use rustc_index::vec::IndexVec; use rustc_middle::hir::map::Map; +/// Traverses the body to find the control flow graph and locations for the +/// relevant places are dropped or reinitialized. +/// +/// The resulting structure still needs to be iterated to a fixed point, which +/// can be done with propagate_to_fixpoint in cfg_propagate. +pub fn build_control_flow_graph<'tcx>( + hir: Map<'tcx>, + consumed_borrowed_places: ConsumedAndBorrowedPlaces, + body: &'tcx Body<'tcx>, + num_exprs: usize, +) -> DropRanges { + let mut drop_range_visitor = DropRangeVisitor::new(hir, consumed_borrowed_places, num_exprs); + intravisit::walk_body(&mut drop_range_visitor, body); + drop_range_visitor.drop_ranges +} + /// This struct is used to gather the information for `DropRanges` to determine the regions of the /// HIR tree for which a value is dropped. /// /// We are interested in points where a variables is dropped or initialized, and the control flow /// of the code. We identify locations in code by their post-order traversal index, so it is /// important for this traversal to match that in `RegionResolutionVisitor` and `InteriorVisitor`. -pub struct DropRangeVisitor<'tcx> { +struct DropRangeVisitor<'tcx> { hir: Map<'tcx>, - /// Maps a HirId to a set of HirIds that are dropped by that node. - /// - /// See also the more detailed comment on `ExprUseDelegate.consumed_places`. - consumed_places: HirIdMap, - borrowed_places: HirIdSet, + places: ConsumedAndBorrowedPlaces, drop_ranges: DropRanges, expr_count: usize, } impl<'tcx> DropRangeVisitor<'tcx> { - pub fn from_uses(uses: ExprUseDelegate<'tcx>, num_exprs: usize) -> Self { - debug!("consumed_places: {:?}", uses.consumed_places); + fn new(hir: Map<'tcx>, places: ConsumedAndBorrowedPlaces, num_exprs: usize) -> Self { + debug!("consumed_places: {:?}", places.consumed); let drop_ranges = DropRanges::new( - uses.consumed_places.iter().flat_map(|(_, places)| places.iter().copied()), - &uses.hir, + places.consumed.iter().flat_map(|(_, places)| places.iter().copied()), + hir, num_exprs, ); - Self { - hir: uses.hir, - consumed_places: uses.consumed_places, - borrowed_places: uses.borrowed_places, - drop_ranges, - expr_count: 0, - } - } - - pub fn into_drop_ranges(self) -> DropRanges { - self.drop_ranges + Self { hir, places, drop_ranges, expr_count: 0 } } fn record_drop(&mut self, hir_id: HirId) { - if self.borrowed_places.contains(&hir_id) { + if self.places.borrowed.contains(&hir_id) { debug!("not marking {:?} as dropped because it is borrowed at some point", hir_id); } else { debug!("marking {:?} as dropped at {}", hir_id, self.expr_count); @@ -59,7 +65,8 @@ impl<'tcx> DropRangeVisitor<'tcx> { fn consume_expr(&mut self, expr: &hir::Expr<'_>) { debug!("consuming expr {:?}, count={}", expr.hir_id, self.expr_count); let places = self - .consumed_places + .places + .consumed .get(&expr.hir_id) .map_or(vec![], |places| places.iter().cloned().collect()); for place in places { @@ -167,3 +174,60 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { self.expr_count += 1; } } + +impl DropRanges { + fn new(hir_ids: impl Iterator, hir: Map<'_>, num_exprs: usize) -> Self { + let mut hir_id_map = HirIdMap::::default(); + let mut next = <_>::from(0u32); + for hir_id in hir_ids { + for_each_consumable(hir_id, hir.find(hir_id), |hir_id| { + if !hir_id_map.contains_key(&hir_id) { + hir_id_map.insert(hir_id, next); + next = <_>::from(next.index() + 1); + } + }); + } + debug!("hir_id_map: {:?}", hir_id_map); + let num_values = hir_id_map.len(); + Self { + hir_id_map, + nodes: IndexVec::from_fn_n(|_| NodeInfo::new(num_values), num_exprs + 1), + deferred_edges: <_>::default(), + post_order_map: <_>::default(), + } + } + + fn hidx(&self, hir_id: HirId) -> HirIdIndex { + *self.hir_id_map.get(&hir_id).unwrap() + } + + /// Adds an entry in the mapping from HirIds to PostOrderIds + /// + /// Needed so that `add_control_edge_hir_id` can work. + fn add_node_mapping(&mut self, hir_id: HirId, post_order_id: usize) { + self.post_order_map.insert(hir_id, post_order_id); + } + + /// Like add_control_edge, but uses a hir_id as the target. + /// + /// This can be used for branches where we do not know the PostOrderId of the target yet, + /// such as when handling `break` or `continue`. + fn add_control_edge_hir_id(&mut self, from: usize, to: HirId) { + self.deferred_edges.push((from, to)); + } + + fn drop_at(&mut self, value: HirId, location: usize) { + let value = self.hidx(value); + self.node_mut(location.into()).drops.push(value); + } + + fn reinit_at(&mut self, value: HirId, location: usize) { + let value = match self.hir_id_map.get(&value) { + Some(value) => *value, + // If there's no value, this is never consumed and therefore is never dropped. We can + // ignore this. + None => return, + }; + self.node_mut(location.into()).reinits.push(value); + } +} diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_propagate.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_propagate.rs index ea7b9106b9a80..74ce762864e0f 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_propagate.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_propagate.rs @@ -1,8 +1,7 @@ -use std::collections::BTreeMap; - -use rustc_index::{bit_set::BitSet, vec::IndexVec}; - use super::{DropRanges, PostOrderId}; +use rustc_index::{bit_set::BitSet, vec::IndexVec}; +use std::collections::BTreeMap; +use std::mem::swap; impl DropRanges { pub fn propagate_to_fixpoint(&mut self) { @@ -64,4 +63,18 @@ impl DropRanges { } preds } + + /// Looks up PostOrderId for any control edges added by HirId and adds a proper edge for them. + /// + /// Should be called after visiting the HIR but before solving the control flow, otherwise some + /// edges will be missed. + fn process_deferred_edges(&mut self) { + let mut edges = vec![]; + swap(&mut edges, &mut self.deferred_edges); + edges.into_iter().for_each(|(from, to)| { + let to = *self.post_order_map.get(&to).expect("Expression ID not found"); + trace!("Adding deferred edge from {} to {}", from, to); + self.add_control_edge(from, to) + }); + } } diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs index 93bb58cd8a099..e8cee21168ab3 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs @@ -2,7 +2,7 @@ use crate::{ check::FnCtxt, expr_use_visitor::{self, ExprUseVisitor}, }; -use hir::{HirId, HirIdMap, HirIdSet, Body, def_id::DefId}; +use hir::{def_id::DefId, Body, HirId, HirIdMap, HirIdSet}; use rustc_hir as hir; use rustc_middle::hir::{ map::Map, @@ -10,12 +10,17 @@ use rustc_middle::hir::{ }; use rustc_middle::ty; -/// Works with ExprUseVisitor to find interesting values for the drop range analysis. -/// -/// Interesting values are those that are either dropped or borrowed. For dropped values, we also -/// record the parent expression, which is the point where the drop actually takes place. -pub struct ExprUseDelegate<'tcx> { - pub(super) hir: Map<'tcx>, +pub fn find_consumed_and_borrowed<'a, 'tcx>( + fcx: &'a FnCtxt<'a, 'tcx>, + def_id: DefId, + body: &'tcx Body<'tcx>, +) -> ConsumedAndBorrowedPlaces { + let mut expr_use_visitor = ExprUseDelegate::new(fcx.tcx.hir()); + expr_use_visitor.consume_body(fcx, def_id, body); + expr_use_visitor.places +} + +pub struct ConsumedAndBorrowedPlaces { /// Records the variables/expressions that are dropped by a given expression. /// /// The key is the hir-id of the expression, and the value is a set or hir-ids for variables @@ -23,17 +28,32 @@ pub struct ExprUseDelegate<'tcx> { /// /// Note that this set excludes "partial drops" -- for example, a statement like `drop(x.y)` is /// not considered a drop of `x`, although it would be a drop of `x.y`. - pub(super) consumed_places: HirIdMap, + pub consumed: HirIdMap, /// A set of hir-ids of values or variables that are borrowed at some point within the body. - pub(super) borrowed_places: HirIdSet, + pub borrowed: HirIdSet, +} + +/// Works with ExprUseVisitor to find interesting values for the drop range analysis. +/// +/// Interesting values are those that are either dropped or borrowed. For dropped values, we also +/// record the parent expression, which is the point where the drop actually takes place. +struct ExprUseDelegate<'tcx> { + hir: Map<'tcx>, + places: ConsumedAndBorrowedPlaces, } impl<'tcx> ExprUseDelegate<'tcx> { - pub fn new(hir: Map<'tcx>) -> Self { - Self { hir, consumed_places: <_>::default(), borrowed_places: <_>::default() } + fn new(hir: Map<'tcx>) -> Self { + Self { + hir, + places: ConsumedAndBorrowedPlaces { + consumed: <_>::default(), + borrowed: <_>::default(), + }, + } } - pub fn consume_body( + fn consume_body( &mut self, fcx: &'_ FnCtxt<'_, 'tcx>, def_id: DefId, @@ -51,10 +71,10 @@ impl<'tcx> ExprUseDelegate<'tcx> { } fn mark_consumed(&mut self, consumer: HirId, target: HirId) { - if !self.consumed_places.contains_key(&consumer) { - self.consumed_places.insert(consumer, <_>::default()); + if !self.places.consumed.contains_key(&consumer) { + self.places.consumed.insert(consumer, <_>::default()); } - self.consumed_places.get_mut(&consumer).map(|places| places.insert(target)); + self.places.consumed.get_mut(&consumer).map(|places| places.insert(target)); } } @@ -82,7 +102,7 @@ impl<'tcx> expr_use_visitor::Delegate<'tcx> for ExprUseDelegate<'tcx> { _diag_expr_id: HirId, _bk: rustc_middle::ty::BorrowKind, ) { - place_hir_id(&place_with_id.place).map(|place| self.borrowed_places.insert(place)); + place_hir_id(&place_with_id.place).map(|place| self.places.borrowed.insert(place)); } fn mutate( From 6a28afb2a3ea199a86bfc98af02df444b40c44ca Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Mon, 13 Dec 2021 16:07:02 -0800 Subject: [PATCH 21/35] Fixing formatting --- .../drop_ranges/record_consumed_borrow.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs index e8cee21168ab3..36e843e7fd1f3 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs @@ -53,12 +53,7 @@ impl<'tcx> ExprUseDelegate<'tcx> { } } - fn consume_body( - &mut self, - fcx: &'_ FnCtxt<'_, 'tcx>, - def_id: DefId, - body: &'tcx Body<'tcx>, - ) { + fn consume_body(&mut self, fcx: &'_ FnCtxt<'_, 'tcx>, def_id: DefId, body: &'tcx Body<'tcx>) { // Run ExprUseVisitor to find where values are consumed. ExprUseVisitor::new( self, From 7d82e4f7642a3675e7dc87a483d79cf02681d930 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Wed, 15 Dec 2021 12:35:34 -0800 Subject: [PATCH 22/35] Update stderr files --- src/test/ui/async-await/unresolved_type_param.stderr | 8 ++++---- src/test/ui/lint/must_not_suspend/dedup.stderr | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/test/ui/async-await/unresolved_type_param.stderr b/src/test/ui/async-await/unresolved_type_param.stderr index 6a268bcda6297..d19a3226ef9a4 100644 --- a/src/test/ui/async-await/unresolved_type_param.stderr +++ b/src/test/ui/async-await/unresolved_type_param.stderr @@ -17,10 +17,10 @@ LL | bar().await; | ^^^ cannot infer type for type parameter `T` declared on the function `bar` | note: the type is part of the `async fn` body because of this `await` - --> $DIR/unresolved_type_param.rs:9:5 + --> $DIR/unresolved_type_param.rs:9:10 | LL | bar().await; - | ^^^^^^^^^^^ + | ^^^^^^ error[E0698]: type inside `async fn` body must be known in this context --> $DIR/unresolved_type_param.rs:9:5 @@ -29,10 +29,10 @@ LL | bar().await; | ^^^ cannot infer type for type parameter `T` declared on the function `bar` | note: the type is part of the `async fn` body because of this `await` - --> $DIR/unresolved_type_param.rs:9:5 + --> $DIR/unresolved_type_param.rs:9:10 | LL | bar().await; - | ^^^^^^^^^^^ + | ^^^^^^ error: aborting due to 3 previous errors diff --git a/src/test/ui/lint/must_not_suspend/dedup.stderr b/src/test/ui/lint/must_not_suspend/dedup.stderr index d15137474527e..13fa3ae3008d2 100644 --- a/src/test/ui/lint/must_not_suspend/dedup.stderr +++ b/src/test/ui/lint/must_not_suspend/dedup.stderr @@ -2,7 +2,7 @@ error: `No` held across a suspend point, but should not be --> $DIR/dedup.rs:16:13 | LL | wheeee(&No {}).await; - | --------^^^^^------- the value is held across this suspend point + | ^^^^^ ------ the value is held across this suspend point | note: the lint level is defined here --> $DIR/dedup.rs:3:9 From 2af02cf2c4061f517d5fc81591c9ae6b53225d24 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Wed, 15 Dec 2021 16:00:48 -0800 Subject: [PATCH 23/35] More comments and refactoring The refactoring mainly keeps the separation between the modules clearer. For example, process_deferred_edges function moved to cfg_build.rs since that is really part of building the CFG, not finding the fixpoint. Also, we use PostOrderId instead of usize in a lot more places now. --- .../check/generator_interior/drop_ranges.rs | 75 ++++++++++++----- .../drop_ranges/cfg_build.rs | 84 +++++++++++-------- .../drop_ranges/cfg_propagate.rs | 25 ++---- .../drop_ranges/cfg_visualize.rs | 8 +- 4 files changed, 117 insertions(+), 75 deletions(-) diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs index b200320b8d3b7..9fbefcfb088c3 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs @@ -41,7 +41,7 @@ pub fn compute_drop_ranges<'a, 'tcx>( drop_ranges.propagate_to_fixpoint(); - drop_ranges + DropRanges { hir_id_map: drop_ranges.hir_id_map, nodes: drop_ranges.nodes } } /// Applies `f` to consumable portion of a HIR node. @@ -77,12 +77,59 @@ rustc_index::newtype_index! { pub struct DropRanges { hir_id_map: HirIdMap, nodes: IndexVec, - deferred_edges: Vec<(usize, HirId)>, - // FIXME: This should only be used for loops and break/continue. - post_order_map: HirIdMap, } -impl Debug for DropRanges { +impl DropRanges { + pub fn is_dropped_at(&self, hir_id: HirId, location: usize) -> bool { + self.hir_id_map + .get(&hir_id) + .copied() + .map_or(false, |hir_id| self.expect_node(location.into()).drop_state.contains(hir_id)) + } + + /// Returns a reference to the NodeInfo for a node, panicking if it does not exist + fn expect_node(&self, id: PostOrderId) -> &NodeInfo { + &self.nodes[id] + } +} + +/// Tracks information needed to compute drop ranges. +struct DropRangesBuilder { + /// The core of DropRangesBuilder is a set of nodes, which each represent + /// one expression. We primarily refer to them by their index in a + /// post-order traversal of the HIR tree, since this is what + /// generator_interior uses to talk about yield positions. + /// + /// This IndexVec keeps the relevant details for each node. See the + /// NodeInfo struct for more details, but this information includes things + /// such as the set of control-flow successors, which variables are dropped + /// or reinitialized, and whether each variable has been inferred to be + /// known-dropped or potentially reintiialized at each point. + nodes: IndexVec, + /// We refer to values whose drop state we are tracking by the HirId of + /// where they are defined. Within a NodeInfo, however, we store the + /// drop-state in a bit vector indexed by a HirIdIndex + /// (see NodeInfo::drop_state). The hir_id_map field stores the mapping + /// from HirIds to the HirIdIndex that is used to represent that value in + /// bitvector. + hir_id_map: HirIdMap, + + /// When building the control flow graph, we don't always know the + /// post-order index of the target node at the point we encounter it. + /// For example, this happens with break and continue. In those cases, + /// we store a pair of the PostOrderId of the source and the HirId + /// of the target. Once we have gathered all of these edges, we make a + /// pass over the set of deferred edges (see process_deferred_edges in + /// cfg_build.rs), look up the PostOrderId for the target (since now the + /// post-order index for all nodes is known), and add missing control flow + /// edges. + deferred_edges: Vec<(PostOrderId, HirId)>, + /// This maps HirIds of expressions to their post-order index. It is + /// used in process_deferred_edges to correctly add back-edges. + post_order_map: HirIdMap, +} + +impl Debug for DropRangesBuilder { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("DropRanges") .field("hir_id_map", &self.hir_id_map) @@ -98,32 +145,20 @@ impl Debug for DropRanges { /// by their index in the post-order traversal. At its core, DropRanges maps /// (hir_id, post_order_id) -> bool, where a true value indicates that the value is definitely /// dropped at the point of the node identified by post_order_id. -impl DropRanges { - pub fn is_dropped_at(&mut self, hir_id: HirId, location: usize) -> bool { - self.hir_id_map - .get(&hir_id) - .copied() - .map_or(false, |hir_id| self.expect_node(location.into()).drop_state.contains(hir_id)) - } - +impl DropRangesBuilder { /// Returns the number of values (hir_ids) that are tracked fn num_values(&self) -> usize { self.hir_id_map.len() } - /// Returns a reference to the NodeInfo for a node, panicking if it does not exist - fn expect_node(&self, id: PostOrderId) -> &NodeInfo { - &self.nodes[id] - } - fn node_mut(&mut self, id: PostOrderId) -> &mut NodeInfo { let size = self.num_values(); self.nodes.ensure_contains_elem(id, || NodeInfo::new(size)); &mut self.nodes[id] } - fn add_control_edge(&mut self, from: usize, to: usize) { - trace!("adding control edge from {} to {}", from, to); + fn add_control_edge(&mut self, from: PostOrderId, to: PostOrderId) { + trace!("adding control edge from {:?} to {:?}", from, to); self.node_mut(from.into()).successors.push(to.into()); } } diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs index e1f1b44283bbe..32f423f3bfeef 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs @@ -1,6 +1,6 @@ use super::{ - for_each_consumable, record_consumed_borrow::ConsumedAndBorrowedPlaces, DropRanges, HirIdIndex, - NodeInfo, + for_each_consumable, record_consumed_borrow::ConsumedAndBorrowedPlaces, DropRangesBuilder, + HirIdIndex, NodeInfo, PostOrderId, }; use hir::{ intravisit::{self, NestedVisitorMap, Visitor}, @@ -9,20 +9,24 @@ use hir::{ use rustc_hir as hir; use rustc_index::vec::IndexVec; use rustc_middle::hir::map::Map; +use std::mem::swap; /// Traverses the body to find the control flow graph and locations for the /// relevant places are dropped or reinitialized. /// /// The resulting structure still needs to be iterated to a fixed point, which /// can be done with propagate_to_fixpoint in cfg_propagate. -pub fn build_control_flow_graph<'tcx>( +pub(super) fn build_control_flow_graph<'tcx>( hir: Map<'tcx>, consumed_borrowed_places: ConsumedAndBorrowedPlaces, body: &'tcx Body<'tcx>, num_exprs: usize, -) -> DropRanges { +) -> DropRangesBuilder { let mut drop_range_visitor = DropRangeVisitor::new(hir, consumed_borrowed_places, num_exprs); intravisit::walk_body(&mut drop_range_visitor, body); + + drop_range_visitor.drop_ranges.process_deferred_edges(); + drop_range_visitor.drop_ranges } @@ -35,27 +39,27 @@ pub fn build_control_flow_graph<'tcx>( struct DropRangeVisitor<'tcx> { hir: Map<'tcx>, places: ConsumedAndBorrowedPlaces, - drop_ranges: DropRanges, - expr_count: usize, + drop_ranges: DropRangesBuilder, + expr_index: PostOrderId, } impl<'tcx> DropRangeVisitor<'tcx> { fn new(hir: Map<'tcx>, places: ConsumedAndBorrowedPlaces, num_exprs: usize) -> Self { debug!("consumed_places: {:?}", places.consumed); - let drop_ranges = DropRanges::new( + let drop_ranges = DropRangesBuilder::new( places.consumed.iter().flat_map(|(_, places)| places.iter().copied()), hir, num_exprs, ); - Self { hir, places, drop_ranges, expr_count: 0 } + Self { hir, places, drop_ranges, expr_index: PostOrderId::from_u32(0) } } fn record_drop(&mut self, hir_id: HirId) { if self.places.borrowed.contains(&hir_id) { debug!("not marking {:?} as dropped because it is borrowed at some point", hir_id); } else { - debug!("marking {:?} as dropped at {}", hir_id, self.expr_count); - let count = self.expr_count; + debug!("marking {:?} as dropped at {:?}", hir_id, self.expr_index); + let count = self.expr_index; self.drop_ranges.drop_at(hir_id, count); } } @@ -63,7 +67,7 @@ impl<'tcx> DropRangeVisitor<'tcx> { /// ExprUseVisitor's consume callback doesn't go deep enough for our purposes in all /// expressions. This method consumes a little deeper into the expression when needed. fn consume_expr(&mut self, expr: &hir::Expr<'_>) { - debug!("consuming expr {:?}, count={}", expr.hir_id, self.expr_count); + debug!("consuming expr {:?}, count={:?}", expr.hir_id, self.expr_index); let places = self .places .consumed @@ -80,8 +84,8 @@ impl<'tcx> DropRangeVisitor<'tcx> { hir::Path { res: hir::def::Res::Local(hir_id), .. }, )) = expr.kind { - let location = self.expr_count; - debug!("reinitializing {:?} at {}", hir_id, location); + let location = self.expr_index; + debug!("reinitializing {:?} at {:?}", hir_id, location); self.drop_ranges.reinit_at(*hir_id, location); } else { debug!("reinitializing {:?} is not supported", expr); @@ -102,18 +106,18 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { ExprKind::If(test, if_true, if_false) => { self.visit_expr(test); - let fork = self.expr_count; + let fork = self.expr_index; - self.drop_ranges.add_control_edge(fork, self.expr_count + 1); + self.drop_ranges.add_control_edge(fork, self.expr_index + 1); self.visit_expr(if_true); - let true_end = self.expr_count; + let true_end = self.expr_index; - self.drop_ranges.add_control_edge(fork, self.expr_count + 1); + self.drop_ranges.add_control_edge(fork, self.expr_index + 1); if let Some(if_false) = if_false { self.visit_expr(if_false); } - self.drop_ranges.add_control_edge(true_end, self.expr_count + 1); + self.drop_ranges.add_control_edge(true_end, self.expr_index + 1); } ExprKind::Assign(lhs, rhs, _) => { self.visit_expr(lhs); @@ -122,18 +126,18 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { reinit = Some(lhs); } ExprKind::Loop(body, ..) => { - let loop_begin = self.expr_count + 1; + let loop_begin = self.expr_index + 1; self.visit_block(body); - self.drop_ranges.add_control_edge(self.expr_count, loop_begin); + self.drop_ranges.add_control_edge(self.expr_index, loop_begin); } ExprKind::Match(scrutinee, arms, ..) => { self.visit_expr(scrutinee); - let fork = self.expr_count; + let fork = self.expr_index; let arm_end_ids = arms .iter() .map(|hir::Arm { pat, body, guard, .. }| { - self.drop_ranges.add_control_edge(fork, self.expr_count + 1); + self.drop_ranges.add_control_edge(fork, self.expr_index + 1); self.visit_pat(pat); match guard { Some(Guard::If(expr)) => self.visit_expr(expr), @@ -144,23 +148,23 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { None => (), } self.visit_expr(body); - self.expr_count + self.expr_index }) .collect::>(); arm_end_ids.into_iter().for_each(|arm_end| { - self.drop_ranges.add_control_edge(arm_end, self.expr_count + 1) + self.drop_ranges.add_control_edge(arm_end, self.expr_index + 1) }); } ExprKind::Break(hir::Destination { target_id: Ok(target), .. }, ..) | ExprKind::Continue(hir::Destination { target_id: Ok(target), .. }, ..) => { - self.drop_ranges.add_control_edge_hir_id(self.expr_count, target); + self.drop_ranges.add_control_edge_hir_id(self.expr_index, target); } _ => intravisit::walk_expr(self, expr), } - self.expr_count += 1; - self.drop_ranges.add_node_mapping(expr.hir_id, self.expr_count); + self.expr_index = self.expr_index + 1; + self.drop_ranges.add_node_mapping(expr.hir_id, self.expr_index); self.consume_expr(expr); if let Some(expr) = reinit { self.reinit_expr(expr); @@ -171,11 +175,11 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { intravisit::walk_pat(self, pat); // Increment expr_count here to match what InteriorVisitor expects. - self.expr_count += 1; + self.expr_index = self.expr_index + 1; } } -impl DropRanges { +impl DropRangesBuilder { fn new(hir_ids: impl Iterator, hir: Map<'_>, num_exprs: usize) -> Self { let mut hir_id_map = HirIdMap::::default(); let mut next = <_>::from(0u32); @@ -204,7 +208,7 @@ impl DropRanges { /// Adds an entry in the mapping from HirIds to PostOrderIds /// /// Needed so that `add_control_edge_hir_id` can work. - fn add_node_mapping(&mut self, hir_id: HirId, post_order_id: usize) { + fn add_node_mapping(&mut self, hir_id: HirId, post_order_id: PostOrderId) { self.post_order_map.insert(hir_id, post_order_id); } @@ -212,16 +216,16 @@ impl DropRanges { /// /// This can be used for branches where we do not know the PostOrderId of the target yet, /// such as when handling `break` or `continue`. - fn add_control_edge_hir_id(&mut self, from: usize, to: HirId) { + fn add_control_edge_hir_id(&mut self, from: PostOrderId, to: HirId) { self.deferred_edges.push((from, to)); } - fn drop_at(&mut self, value: HirId, location: usize) { + fn drop_at(&mut self, value: HirId, location: PostOrderId) { let value = self.hidx(value); self.node_mut(location.into()).drops.push(value); } - fn reinit_at(&mut self, value: HirId, location: usize) { + fn reinit_at(&mut self, value: HirId, location: PostOrderId) { let value = match self.hir_id_map.get(&value) { Some(value) => *value, // If there's no value, this is never consumed and therefore is never dropped. We can @@ -230,4 +234,18 @@ impl DropRanges { }; self.node_mut(location.into()).reinits.push(value); } + + /// Looks up PostOrderId for any control edges added by HirId and adds a proper edge for them. + /// + /// Should be called after visiting the HIR but before solving the control flow, otherwise some + /// edges will be missed. + fn process_deferred_edges(&mut self) { + let mut edges = vec![]; + swap(&mut edges, &mut self.deferred_edges); + edges.into_iter().for_each(|(from, to)| { + let to = *self.post_order_map.get(&to).expect("Expression ID not found"); + trace!("Adding deferred edge from {:?} to {:?}", from, to); + self.add_control_edge(from, to) + }); + } } diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_propagate.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_propagate.rs index 74ce762864e0f..22f7484abf3e2 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_propagate.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_propagate.rs @@ -1,12 +1,10 @@ -use super::{DropRanges, PostOrderId}; +use super::{DropRangesBuilder, PostOrderId}; use rustc_index::{bit_set::BitSet, vec::IndexVec}; use std::collections::BTreeMap; -use std::mem::swap; -impl DropRanges { +impl DropRangesBuilder { pub fn propagate_to_fixpoint(&mut self) { trace!("before fixpoint: {:#?}", self); - self.process_deferred_edges(); let preds = self.compute_predecessors(); trace!("predecessors: {:#?}", preds.iter_enumerated().collect::>()); @@ -53,6 +51,11 @@ impl DropRanges { fn compute_predecessors(&self) -> IndexVec> { let mut preds = IndexVec::from_fn_n(|_| vec![], self.nodes.len()); for (id, node) in self.nodes.iter_enumerated() { + // If the node has no explicit successors, we assume that control + // will from this node into the next one. + // + // If there are successors listed, then we assume that all + // possible successors are given and we do not include the default. if node.successors.len() == 0 && id.index() != self.nodes.len() - 1 { preds[id + 1].push(id); } else { @@ -63,18 +66,4 @@ impl DropRanges { } preds } - - /// Looks up PostOrderId for any control edges added by HirId and adds a proper edge for them. - /// - /// Should be called after visiting the HIR but before solving the control flow, otherwise some - /// edges will be missed. - fn process_deferred_edges(&mut self) { - let mut edges = vec![]; - swap(&mut edges, &mut self.deferred_edges); - edges.into_iter().for_each(|(from, to)| { - let to = *self.post_order_map.get(&to).expect("Expression ID not found"); - trace!("Adding deferred edge from {} to {}", from, to); - self.add_control_edge(from, to) - }); - } } diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_visualize.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_visualize.rs index ebbbec1c472b6..b87b3dd9a5f96 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_visualize.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_visualize.rs @@ -3,9 +3,9 @@ use rustc_graphviz as dot; -use super::{DropRanges, PostOrderId}; +use super::{DropRangesBuilder, PostOrderId}; -impl<'a> dot::GraphWalk<'a> for DropRanges { +impl<'a> dot::GraphWalk<'a> for DropRangesBuilder { type Node = PostOrderId; type Edge = (PostOrderId, PostOrderId); @@ -36,7 +36,7 @@ impl<'a> dot::GraphWalk<'a> for DropRanges { } } -impl<'a> dot::Labeller<'a> for DropRanges { +impl<'a> dot::Labeller<'a> for DropRangesBuilder { type Node = PostOrderId; type Edge = (PostOrderId, PostOrderId); @@ -56,7 +56,7 @@ impl<'a> dot::Labeller<'a> for DropRanges { n, self.post_order_map .iter() - .find(|(_hir_id, &post_order_id)| post_order_id == n.index()) + .find(|(_hir_id, &post_order_id)| post_order_id == *n) .map_or("".into(), |(hir_id, _)| format!( "{}", hir_id.local_id.index() From 4a70de79321552fa8c254b9998562d7814f3f72e Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Thu, 16 Dec 2021 12:07:36 -0800 Subject: [PATCH 24/35] Handle reinits in match guards --- .../drop_ranges/cfg_build.rs | 21 ++++++++++------ .../drop_ranges/cfg_visualize.rs | 9 +++++++ .../ui/generator/reinit-in-match-guard.rs | 25 +++++++++++++++++++ 3 files changed, 47 insertions(+), 8 deletions(-) create mode 100644 src/test/ui/generator/reinit-in-match-guard.rs diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs index 32f423f3bfeef..0520931d5f605 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs @@ -133,11 +133,10 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { ExprKind::Match(scrutinee, arms, ..) => { self.visit_expr(scrutinee); - let fork = self.expr_index; - let arm_end_ids = arms - .iter() - .map(|hir::Arm { pat, body, guard, .. }| { - self.drop_ranges.add_control_edge(fork, self.expr_index + 1); + let (guard_exit, arm_end_ids) = arms.iter().fold( + (self.expr_index, vec![]), + |(incoming_edge, mut arm_end_ids), hir::Arm { pat, body, guard, .. }| { + self.drop_ranges.add_control_edge(incoming_edge, self.expr_index + 1); self.visit_pat(pat); match guard { Some(Guard::If(expr)) => self.visit_expr(expr), @@ -147,10 +146,16 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { } None => (), } + let to_next_arm = self.expr_index; + // The default edge does not get added since we also have an explicit edge, + // so we also need to add an edge to the next node as well. + self.drop_ranges.add_control_edge(self.expr_index, self.expr_index + 1); self.visit_expr(body); - self.expr_index - }) - .collect::>(); + arm_end_ids.push(self.expr_index); + (to_next_arm, arm_end_ids) + }, + ); + self.drop_ranges.add_control_edge(guard_exit, self.expr_index + 1); arm_end_ids.into_iter().for_each(|arm_end| { self.drop_ranges.add_control_edge(arm_end, self.expr_index + 1) }); diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_visualize.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_visualize.rs index b87b3dd9a5f96..20aad7aedf775 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_visualize.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_visualize.rs @@ -5,6 +5,15 @@ use rustc_graphviz as dot; use super::{DropRangesBuilder, PostOrderId}; +/// Writes the CFG for DropRangesBuilder to a .dot file for visualization. +/// +/// It is not normally called, but is kept around to easily add debugging +/// code when needed. +#[allow(dead_code)] +pub(super) fn write_graph_to_file(drop_ranges: &DropRangesBuilder, filename: &str) { + dot::render(drop_ranges, &mut std::fs::File::create(filename).unwrap()).unwrap(); +} + impl<'a> dot::GraphWalk<'a> for DropRangesBuilder { type Node = PostOrderId; diff --git a/src/test/ui/generator/reinit-in-match-guard.rs b/src/test/ui/generator/reinit-in-match-guard.rs new file mode 100644 index 0000000000000..260b341a52525 --- /dev/null +++ b/src/test/ui/generator/reinit-in-match-guard.rs @@ -0,0 +1,25 @@ +// build-pass + +#![feature(generators)] + +#![allow(unused_assignments, dead_code)] + +fn main() { + let _ = || { + let mut x = vec![22_usize]; + std::mem::drop(x); + match y() { + true if { + x = vec![]; + false + } => {} + _ => { + yield; + } + } + }; +} + +fn y() -> bool { + true +} From 6e281a7782b775104351ab28abaee795fdaf84c5 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Thu, 16 Dec 2021 12:38:08 -0800 Subject: [PATCH 25/35] Explicitly list all ExprKinds in cfg_build Also rearranges the existing arms to be more logical. For example, Break and Continue come closer to Loop now. --- .../drop_ranges/cfg_build.rs | 52 ++++++++++++++----- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs index 0520931d5f605..4656f56569e46 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs @@ -103,6 +103,12 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { let mut reinit = None; match expr.kind { + ExprKind::Assign(lhs, rhs, _) => { + self.visit_expr(lhs); + self.visit_expr(rhs); + + reinit = Some(lhs); + } ExprKind::If(test, if_true, if_false) => { self.visit_expr(test); @@ -119,17 +125,6 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { self.drop_ranges.add_control_edge(true_end, self.expr_index + 1); } - ExprKind::Assign(lhs, rhs, _) => { - self.visit_expr(lhs); - self.visit_expr(rhs); - - reinit = Some(lhs); - } - ExprKind::Loop(body, ..) => { - let loop_begin = self.expr_index + 1; - self.visit_block(body); - self.drop_ranges.add_control_edge(self.expr_index, loop_begin); - } ExprKind::Match(scrutinee, arms, ..) => { self.visit_expr(scrutinee); @@ -160,12 +155,45 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { self.drop_ranges.add_control_edge(arm_end, self.expr_index + 1) }); } + ExprKind::Loop(body, ..) => { + let loop_begin = self.expr_index + 1; + self.visit_block(body); + self.drop_ranges.add_control_edge(self.expr_index, loop_begin); + } ExprKind::Break(hir::Destination { target_id: Ok(target), .. }, ..) | ExprKind::Continue(hir::Destination { target_id: Ok(target), .. }, ..) => { self.drop_ranges.add_control_edge_hir_id(self.expr_index, target); } - _ => intravisit::walk_expr(self, expr), + ExprKind::AddrOf(..) + | ExprKind::Array(..) + | ExprKind::AssignOp(..) + | ExprKind::Binary(..) + | ExprKind::Block(..) + | ExprKind::Box(..) + | ExprKind::Break(..) + | ExprKind::Call(..) + | ExprKind::Cast(..) + | ExprKind::Closure(..) + | ExprKind::ConstBlock(..) + | ExprKind::Continue(..) + | ExprKind::DropTemps(..) + | ExprKind::Err + | ExprKind::Field(..) + | ExprKind::Index(..) + | ExprKind::InlineAsm(..) + | ExprKind::Let(..) + | ExprKind::Lit(..) + | ExprKind::LlvmInlineAsm(..) + | ExprKind::MethodCall(..) + | ExprKind::Path(..) + | ExprKind::Repeat(..) + | ExprKind::Ret(..) + | ExprKind::Struct(..) + | ExprKind::Tup(..) + | ExprKind::Type(..) + | ExprKind::Unary(..) + | ExprKind::Yield(..) => intravisit::walk_expr(self, expr), } self.expr_index = self.expr_index + 1; From a7df4e8d2f89379fb0b620cb0267f97c05bc1598 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Thu, 16 Dec 2021 13:34:39 -0800 Subject: [PATCH 26/35] Handle empty loops better --- .../check/generator_interior/drop_ranges/cfg_build.rs | 11 +++++++++-- src/test/ui/async-await/async-fn-nonsend.rs | 8 ++++++++ src/test/ui/async-await/async-fn-nonsend.stderr | 8 ++++---- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs index 4656f56569e46..b434e05db80b4 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs @@ -157,8 +157,15 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { } ExprKind::Loop(body, ..) => { let loop_begin = self.expr_index + 1; - self.visit_block(body); - self.drop_ranges.add_control_edge(self.expr_index, loop_begin); + if body.stmts.is_empty() && body.expr.is_none() { + // For empty loops we won't have updated self.expr_index after visiting the + // body, meaning we'd get an edge from expr_index to expr_index + 1, but + // instead we want an edge from expr_index + 1 to expr_index + 1. + self.drop_ranges.add_control_edge(loop_begin, loop_begin); + } else { + self.visit_block(body); + self.drop_ranges.add_control_edge(self.expr_index, loop_begin); + } } ExprKind::Break(hir::Destination { target_id: Ok(target), .. }, ..) | ExprKind::Continue(hir::Destination { target_id: Ok(target), .. }, ..) => { diff --git a/src/test/ui/async-await/async-fn-nonsend.rs b/src/test/ui/async-await/async-fn-nonsend.rs index 210d9ff3f2d32..55629132e400e 100644 --- a/src/test/ui/async-await/async-fn-nonsend.rs +++ b/src/test/ui/async-await/async-fn-nonsend.rs @@ -43,6 +43,13 @@ async fn non_sync_with_method_call() { } } +async fn non_sync_with_infinite_loop() { + let f: &mut std::fmt::Formatter = loop {}; + if non_sync().fmt(f).unwrap() == () { + fut().await; + } +} + fn assert_send(_: impl Send) {} pub fn pass_assert() { @@ -51,4 +58,5 @@ pub fn pass_assert() { //~^ ERROR future cannot be sent between threads safely assert_send(non_sync_with_method_call()); //~^ ERROR future cannot be sent between threads safely + assert_send(non_sync_with_infinite_loop()); } diff --git a/src/test/ui/async-await/async-fn-nonsend.stderr b/src/test/ui/async-await/async-fn-nonsend.stderr index abba5585c62eb..9c87067a4d3a0 100644 --- a/src/test/ui/async-await/async-fn-nonsend.stderr +++ b/src/test/ui/async-await/async-fn-nonsend.stderr @@ -1,5 +1,5 @@ error: future cannot be sent between threads safely - --> $DIR/async-fn-nonsend.rs:50:17 + --> $DIR/async-fn-nonsend.rs:57:17 | LL | assert_send(non_send_temporary_in_match()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ future returned by `non_send_temporary_in_match` is not `Send` @@ -16,13 +16,13 @@ LL | Some(_) => fut().await, LL | } | - `Some(non_send())` is later dropped here note: required by a bound in `assert_send` - --> $DIR/async-fn-nonsend.rs:46:24 + --> $DIR/async-fn-nonsend.rs:53:24 | LL | fn assert_send(_: impl Send) {} | ^^^^ required by this bound in `assert_send` error: future cannot be sent between threads safely - --> $DIR/async-fn-nonsend.rs:52:17 + --> $DIR/async-fn-nonsend.rs:59:17 | LL | assert_send(non_sync_with_method_call()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ future returned by `non_sync_with_method_call` is not `Send` @@ -40,7 +40,7 @@ LL | } LL | } | - `f` is later dropped here note: required by a bound in `assert_send` - --> $DIR/async-fn-nonsend.rs:46:24 + --> $DIR/async-fn-nonsend.rs:53:24 | LL | fn assert_send(_: impl Send) {} | ^^^^ required by this bound in `assert_send` From 7d11b336f3ee9d65ba82b86d73cf9bc9fb174269 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Thu, 16 Dec 2021 15:46:56 -0800 Subject: [PATCH 27/35] Remove clones and most allocations from propagate_to_fixpoint --- .../generator_interior/drop_ranges/cfg_propagate.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_propagate.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_propagate.rs index 22f7484abf3e2..a540812f4a329 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_propagate.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_propagate.rs @@ -9,16 +9,17 @@ impl DropRangesBuilder { trace!("predecessors: {:#?}", preds.iter_enumerated().collect::>()); + let mut new_state = BitSet::new_empty(self.num_values()); + let mut propagate = || { let mut changed = false; for id in self.nodes.indices() { - let old_state = self.nodes[id].drop_state.clone(); - let mut new_state = if id.index() == 0 { - BitSet::new_empty(self.num_values()) + if id.index() == 0 { + new_state.clear(); } else { // If we are not the start node and we have no predecessors, treat // everything as dropped because there's no way to get here anyway. - BitSet::new_filled(self.num_values()) + new_state.insert_all(); }; for pred in &preds[id] { @@ -34,8 +35,7 @@ impl DropRangesBuilder { new_state.remove(*reinit); } - changed |= old_state != new_state; - self.nodes[id].drop_state = new_state; + changed |= self.nodes[id].drop_state.intersect(&new_state); } changed From f730bd0dad555e2a5db4b0110b0888ca515f6fef Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Thu, 16 Dec 2021 17:15:35 -0800 Subject: [PATCH 28/35] Track changed bitsets in CFG propagation This reduces the amount of work done, especially in later iterations, by only processing nodes whose predecessors changed in the previous iteration, or earlier in the current iteration. This also has the side effect of completely ignoring all unreachable nodes. --- .../drop_ranges/cfg_propagate.rs | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_propagate.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_propagate.rs index a540812f4a329..139d17d2e1ca1 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_propagate.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_propagate.rs @@ -10,10 +10,28 @@ impl DropRangesBuilder { trace!("predecessors: {:#?}", preds.iter_enumerated().collect::>()); let mut new_state = BitSet::new_empty(self.num_values()); + let mut changed_nodes = BitSet::new_empty(self.nodes.len()); + let mut unchanged_mask = BitSet::new_filled(self.nodes.len()); + changed_nodes.insert(0u32.into()); let mut propagate = || { let mut changed = false; + unchanged_mask.insert_all(); for id in self.nodes.indices() { + trace!("processing {:?}, changed_nodes: {:?}", id, changed_nodes); + // Check if any predecessor has changed, and if not then short-circuit. + // + // We handle the start node specially, since it doesn't have any predecessors, + // but we need to start somewhere. + if match id.index() { + 0 => !changed_nodes.contains(id), + _ => !preds[id].iter().any(|pred| changed_nodes.contains(*pred)), + } { + trace!("short-circuiting because none of {:?} have changed", preds[id]); + unchanged_mask.remove(id); + continue; + } + if id.index() == 0 { new_state.clear(); } else { @@ -23,8 +41,7 @@ impl DropRangesBuilder { }; for pred in &preds[id] { - let state = &self.nodes[*pred].drop_state; - new_state.intersect(state); + new_state.intersect(&self.nodes[*pred].drop_state); } for drop in &self.nodes[id].drops { @@ -35,9 +52,15 @@ impl DropRangesBuilder { new_state.remove(*reinit); } - changed |= self.nodes[id].drop_state.intersect(&new_state); + if self.nodes[id].drop_state.intersect(&new_state) { + changed_nodes.insert(id); + changed = true; + } else { + unchanged_mask.remove(id); + } } + changed_nodes.intersect(&unchanged_mask); changed }; From 787f4cbd15b91e88d757bf3f1ac1dadfa0e8ec5a Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Fri, 17 Dec 2021 14:36:51 -0800 Subject: [PATCH 29/35] Handle uninhabited return types This changes drop range analysis to handle uninhabited return types such as `!`. Since these calls to these functions do not return, we model them as ending in an infinite loop. --- .../check/generator_interior/drop_ranges.rs | 10 ++- .../drop_ranges/cfg_build.rs | 62 ++++++++++++++++--- src/test/ui/async-await/async-fn-nonsend.rs | 2 - .../ui/async-await/async-fn-nonsend.stderr | 30 +-------- 4 files changed, 64 insertions(+), 40 deletions(-) diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs index 9fbefcfb088c3..cf463d0aeaebd 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs @@ -36,8 +36,14 @@ pub fn compute_drop_ranges<'a, 'tcx>( let consumed_borrowed_places = find_consumed_and_borrowed(fcx, def_id, body); let num_exprs = fcx.tcx.region_scope_tree(def_id).body_expr_count(body.id()).unwrap_or(0); - let mut drop_ranges = - build_control_flow_graph(fcx.tcx.hir(), consumed_borrowed_places, body, num_exprs); + let mut drop_ranges = build_control_flow_graph( + fcx.tcx.hir(), + fcx.tcx, + &fcx.typeck_results.borrow(), + consumed_borrowed_places, + body, + num_exprs, + ); drop_ranges.propagate_to_fixpoint(); diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs index b434e05db80b4..1591b144dc62b 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs @@ -8,7 +8,10 @@ use hir::{ }; use rustc_hir as hir; use rustc_index::vec::IndexVec; -use rustc_middle::hir::map::Map; +use rustc_middle::{ + hir::map::Map, + ty::{TyCtxt, TypeckResults}, +}; use std::mem::swap; /// Traverses the body to find the control flow graph and locations for the @@ -18,11 +21,14 @@ use std::mem::swap; /// can be done with propagate_to_fixpoint in cfg_propagate. pub(super) fn build_control_flow_graph<'tcx>( hir: Map<'tcx>, + tcx: TyCtxt<'tcx>, + typeck_results: &TypeckResults<'tcx>, consumed_borrowed_places: ConsumedAndBorrowedPlaces, body: &'tcx Body<'tcx>, num_exprs: usize, ) -> DropRangesBuilder { - let mut drop_range_visitor = DropRangeVisitor::new(hir, consumed_borrowed_places, num_exprs); + let mut drop_range_visitor = + DropRangeVisitor::new(hir, tcx, typeck_results, consumed_borrowed_places, num_exprs); intravisit::walk_body(&mut drop_range_visitor, body); drop_range_visitor.drop_ranges.process_deferred_edges(); @@ -36,22 +42,30 @@ pub(super) fn build_control_flow_graph<'tcx>( /// We are interested in points where a variables is dropped or initialized, and the control flow /// of the code. We identify locations in code by their post-order traversal index, so it is /// important for this traversal to match that in `RegionResolutionVisitor` and `InteriorVisitor`. -struct DropRangeVisitor<'tcx> { +struct DropRangeVisitor<'a, 'tcx> { hir: Map<'tcx>, places: ConsumedAndBorrowedPlaces, drop_ranges: DropRangesBuilder, expr_index: PostOrderId, + tcx: TyCtxt<'tcx>, + typeck_results: &'a TypeckResults<'tcx>, } -impl<'tcx> DropRangeVisitor<'tcx> { - fn new(hir: Map<'tcx>, places: ConsumedAndBorrowedPlaces, num_exprs: usize) -> Self { +impl<'a, 'tcx> DropRangeVisitor<'a, 'tcx> { + fn new( + hir: Map<'tcx>, + tcx: TyCtxt<'tcx>, + typeck_results: &'a TypeckResults<'tcx>, + places: ConsumedAndBorrowedPlaces, + num_exprs: usize, + ) -> Self { debug!("consumed_places: {:?}", places.consumed); let drop_ranges = DropRangesBuilder::new( places.consumed.iter().flat_map(|(_, places)| places.iter().copied()), hir, num_exprs, ); - Self { hir, places, drop_ranges, expr_index: PostOrderId::from_u32(0) } + Self { hir, places, drop_ranges, expr_index: PostOrderId::from_u32(0), typeck_results, tcx } } fn record_drop(&mut self, hir_id: HirId) { @@ -91,9 +105,23 @@ impl<'tcx> DropRangeVisitor<'tcx> { debug!("reinitializing {:?} is not supported", expr); } } + + /// For an expression with an uninhabited return type (e.g. a function that returns !), + /// this adds a self edge to to the CFG to model the fact that the function does not + /// return. + fn handle_uninhabited_return(&mut self, expr: &Expr<'tcx>) { + let ty = self.typeck_results.expr_ty(expr); + let ty = self.tcx.erase_regions(ty); + let m = self.tcx.parent_module(expr.hir_id).to_def_id(); + let param_env = self.tcx.param_env(m.expect_local()); + if self.tcx.is_ty_uninhabited_from(m, ty, param_env) { + // This function will not return. We model this fact as an infinite loop. + self.drop_ranges.add_control_edge(self.expr_index + 1, self.expr_index + 1); + } + } } -impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { +impl<'a, 'tcx> Visitor<'tcx> for DropRangeVisitor<'a, 'tcx> { type Map = intravisit::ErasedMap<'tcx>; fn nested_visit_map(&mut self) -> NestedVisitorMap { @@ -109,6 +137,7 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { reinit = Some(lhs); } + ExprKind::If(test, if_true, if_false) => { self.visit_expr(test); @@ -155,6 +184,7 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { self.drop_ranges.add_control_edge(arm_end, self.expr_index + 1) }); } + ExprKind::Loop(body, ..) => { let loop_begin = self.expr_index + 1; if body.stmts.is_empty() && body.expr.is_none() { @@ -172,6 +202,22 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { self.drop_ranges.add_control_edge_hir_id(self.expr_index, target); } + ExprKind::Call(f, args) => { + self.visit_expr(f); + for arg in args { + self.visit_expr(arg); + } + + self.handle_uninhabited_return(expr); + } + ExprKind::MethodCall(_, _, exprs, _) => { + for expr in exprs { + self.visit_expr(expr); + } + + self.handle_uninhabited_return(expr); + } + ExprKind::AddrOf(..) | ExprKind::Array(..) | ExprKind::AssignOp(..) @@ -179,7 +225,6 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { | ExprKind::Block(..) | ExprKind::Box(..) | ExprKind::Break(..) - | ExprKind::Call(..) | ExprKind::Cast(..) | ExprKind::Closure(..) | ExprKind::ConstBlock(..) @@ -192,7 +237,6 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { | ExprKind::Let(..) | ExprKind::Lit(..) | ExprKind::LlvmInlineAsm(..) - | ExprKind::MethodCall(..) | ExprKind::Path(..) | ExprKind::Repeat(..) | ExprKind::Ret(..) diff --git a/src/test/ui/async-await/async-fn-nonsend.rs b/src/test/ui/async-await/async-fn-nonsend.rs index 55629132e400e..123cadc2cbb04 100644 --- a/src/test/ui/async-await/async-fn-nonsend.rs +++ b/src/test/ui/async-await/async-fn-nonsend.rs @@ -36,7 +36,6 @@ async fn non_send_temporary_in_match() { } async fn non_sync_with_method_call() { - // FIXME: it would be nice for this to work let f: &mut std::fmt::Formatter = panic!(); if non_sync().fmt(f).unwrap() == () { fut().await; @@ -57,6 +56,5 @@ pub fn pass_assert() { assert_send(non_send_temporary_in_match()); //~^ ERROR future cannot be sent between threads safely assert_send(non_sync_with_method_call()); - //~^ ERROR future cannot be sent between threads safely assert_send(non_sync_with_infinite_loop()); } diff --git a/src/test/ui/async-await/async-fn-nonsend.stderr b/src/test/ui/async-await/async-fn-nonsend.stderr index 9c87067a4d3a0..be42d46a9060e 100644 --- a/src/test/ui/async-await/async-fn-nonsend.stderr +++ b/src/test/ui/async-await/async-fn-nonsend.stderr @@ -1,5 +1,5 @@ error: future cannot be sent between threads safely - --> $DIR/async-fn-nonsend.rs:57:17 + --> $DIR/async-fn-nonsend.rs:56:17 | LL | assert_send(non_send_temporary_in_match()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ future returned by `non_send_temporary_in_match` is not `Send` @@ -16,34 +16,10 @@ LL | Some(_) => fut().await, LL | } | - `Some(non_send())` is later dropped here note: required by a bound in `assert_send` - --> $DIR/async-fn-nonsend.rs:53:24 + --> $DIR/async-fn-nonsend.rs:52:24 | LL | fn assert_send(_: impl Send) {} | ^^^^ required by this bound in `assert_send` -error: future cannot be sent between threads safely - --> $DIR/async-fn-nonsend.rs:59:17 - | -LL | assert_send(non_sync_with_method_call()); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ future returned by `non_sync_with_method_call` is not `Send` - | - = help: the trait `Send` is not implemented for `dyn std::fmt::Write` -note: future is not `Send` as this value is used across an await - --> $DIR/async-fn-nonsend.rs:42:14 - | -LL | let f: &mut std::fmt::Formatter = panic!(); - | - has type `&mut Formatter<'_>` which is not `Send` -LL | if non_sync().fmt(f).unwrap() == () { -LL | fut().await; - | ^^^^^^ await occurs here, with `f` maybe used later -LL | } -LL | } - | - `f` is later dropped here -note: required by a bound in `assert_send` - --> $DIR/async-fn-nonsend.rs:53:24 - | -LL | fn assert_send(_: impl Send) {} - | ^^^^ required by this bound in `assert_send` - -error: aborting due to 2 previous errors +error: aborting due to previous error From 887e843eeb35e9cc78884e9d5feacf914377f355 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Fri, 17 Dec 2021 15:05:38 -0800 Subject: [PATCH 30/35] Update async-fn-nonsend.rs The previous commit made the non_sync_with_method_call case pass due to the await being unreachable. Unfortunately, this isn't actually the behavior the test was verifying. This change lifts the panic into a helper function so that the generator analysis still thinks the await is reachable, and therefore we preserve the same testing behavior. --- src/test/ui/async-await/async-fn-nonsend.rs | 18 +++++++++-- .../ui/async-await/async-fn-nonsend.stderr | 30 +++++++++++++++++-- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/src/test/ui/async-await/async-fn-nonsend.rs b/src/test/ui/async-await/async-fn-nonsend.rs index 123cadc2cbb04..c5453b67ef5b6 100644 --- a/src/test/ui/async-await/async-fn-nonsend.rs +++ b/src/test/ui/async-await/async-fn-nonsend.rs @@ -35,14 +35,26 @@ async fn non_send_temporary_in_match() { } } +fn get_formatter() -> std::fmt::Formatter<'static> { + panic!() +} + async fn non_sync_with_method_call() { + let f: &mut std::fmt::Formatter = &mut get_formatter(); + // It would by nice for this to work. + if non_sync().fmt(f).unwrap() == () { + fut().await; + } +} + +async fn non_sync_with_method_call_panic() { let f: &mut std::fmt::Formatter = panic!(); if non_sync().fmt(f).unwrap() == () { fut().await; } } -async fn non_sync_with_infinite_loop() { +async fn non_sync_with_method_call_infinite_loop() { let f: &mut std::fmt::Formatter = loop {}; if non_sync().fmt(f).unwrap() == () { fut().await; @@ -56,5 +68,7 @@ pub fn pass_assert() { assert_send(non_send_temporary_in_match()); //~^ ERROR future cannot be sent between threads safely assert_send(non_sync_with_method_call()); - assert_send(non_sync_with_infinite_loop()); + //~^ ERROR future cannot be sent between threads safely + assert_send(non_sync_with_method_call_panic()); + assert_send(non_sync_with_method_call_infinite_loop()); } diff --git a/src/test/ui/async-await/async-fn-nonsend.stderr b/src/test/ui/async-await/async-fn-nonsend.stderr index be42d46a9060e..40ad46b48620d 100644 --- a/src/test/ui/async-await/async-fn-nonsend.stderr +++ b/src/test/ui/async-await/async-fn-nonsend.stderr @@ -1,5 +1,5 @@ error: future cannot be sent between threads safely - --> $DIR/async-fn-nonsend.rs:56:17 + --> $DIR/async-fn-nonsend.rs:68:17 | LL | assert_send(non_send_temporary_in_match()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ future returned by `non_send_temporary_in_match` is not `Send` @@ -16,10 +16,34 @@ LL | Some(_) => fut().await, LL | } | - `Some(non_send())` is later dropped here note: required by a bound in `assert_send` - --> $DIR/async-fn-nonsend.rs:52:24 + --> $DIR/async-fn-nonsend.rs:64:24 | LL | fn assert_send(_: impl Send) {} | ^^^^ required by this bound in `assert_send` -error: aborting due to previous error +error: future cannot be sent between threads safely + --> $DIR/async-fn-nonsend.rs:70:17 + | +LL | assert_send(non_sync_with_method_call()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ future returned by `non_sync_with_method_call` is not `Send` + | + = help: the trait `Send` is not implemented for `dyn std::fmt::Write` +note: future is not `Send` as this value is used across an await + --> $DIR/async-fn-nonsend.rs:46:14 + | +LL | let f: &mut std::fmt::Formatter = &mut get_formatter(); + | --------------- has type `Formatter<'_>` which is not `Send` +... +LL | fut().await; + | ^^^^^^ await occurs here, with `get_formatter()` maybe used later +LL | } +LL | } + | - `get_formatter()` is later dropped here +note: required by a bound in `assert_send` + --> $DIR/async-fn-nonsend.rs:64:24 + | +LL | fn assert_send(_: impl Send) {} + | ^^^^ required by this bound in `assert_send` + +error: aborting due to 2 previous errors From 78c5644de5ffea9d64200bd28eac7e49ca2c8f33 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Mon, 20 Dec 2021 15:50:31 -0800 Subject: [PATCH 31/35] drop_ranges: Add TrackedValue enum This makes it clearer what values we are tracking and why. --- .../check/generator_interior/drop_ranges.rs | 75 +++++++++++++++---- .../drop_ranges/cfg_build.rs | 63 +++++++++------- .../drop_ranges/record_consumed_borrow.rs | 35 +++------ 3 files changed, 106 insertions(+), 67 deletions(-) diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs index cf463d0aeaebd..681cd7cf935f5 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs @@ -17,9 +17,12 @@ use self::record_consumed_borrow::find_consumed_and_borrowed; use crate::check::FnCtxt; use hir::def_id::DefId; use hir::{Body, HirId, HirIdMap, Node}; +use rustc_data_structures::fx::FxHashMap; use rustc_hir as hir; use rustc_index::bit_set::BitSet; use rustc_index::vec::IndexVec; +use rustc_middle::hir::place::{PlaceBase, PlaceWithHirId}; +use rustc_middle::ty; use std::collections::BTreeMap; use std::fmt::Debug; @@ -47,13 +50,17 @@ pub fn compute_drop_ranges<'a, 'tcx>( drop_ranges.propagate_to_fixpoint(); - DropRanges { hir_id_map: drop_ranges.hir_id_map, nodes: drop_ranges.nodes } + DropRanges { tracked_value_map: drop_ranges.tracked_value_map, nodes: drop_ranges.nodes } } /// Applies `f` to consumable portion of a HIR node. /// /// The `node` parameter should be the result of calling `Map::find(place)`. -fn for_each_consumable(place: HirId, node: Option>, mut f: impl FnMut(HirId)) { +fn for_each_consumable( + place: TrackedValue, + node: Option>, + mut f: impl FnMut(TrackedValue), +) { f(place); if let Some(Node::Expr(expr)) = node { match expr.kind { @@ -61,7 +68,7 @@ fn for_each_consumable(place: HirId, node: Option>, mut f: impl FnMut(H _, hir::Path { res: hir::def::Res::Local(hir_id), .. }, )) => { - f(*hir_id); + f(TrackedValue::Variable(*hir_id)); } _ => (), } @@ -75,22 +82,60 @@ rustc_index::newtype_index! { } rustc_index::newtype_index! { - pub struct HirIdIndex { + pub struct TrackedValueIndex { DEBUG_FORMAT = "hidx({})", } } +/// Identifies a value whose drop state we need to track. +#[derive(PartialEq, Eq, Hash, Debug, Clone, Copy)] +enum TrackedValue { + /// Represents a named variable, such as a let binding, parameter, or upvar. + /// + /// The HirId points to the variable's definition site. + Variable(HirId), + /// A value produced as a result of an expression. + /// + /// The HirId points to the expression that returns this value. + Temporary(HirId), +} + +impl TrackedValue { + fn hir_id(&self) -> HirId { + match self { + TrackedValue::Variable(hir_id) | TrackedValue::Temporary(hir_id) => *hir_id, + } + } +} + +impl From<&PlaceWithHirId<'_>> for TrackedValue { + fn from(place_with_id: &PlaceWithHirId<'_>) -> Self { + match place_with_id.place.base { + PlaceBase::Rvalue | PlaceBase::StaticItem => { + TrackedValue::Temporary(place_with_id.hir_id) + } + PlaceBase::Local(hir_id) + | PlaceBase::Upvar(ty::UpvarId { var_path: ty::UpvarPath { hir_id }, .. }) => { + TrackedValue::Variable(hir_id) + } + } + } +} + pub struct DropRanges { - hir_id_map: HirIdMap, + tracked_value_map: FxHashMap, nodes: IndexVec, } impl DropRanges { pub fn is_dropped_at(&self, hir_id: HirId, location: usize) -> bool { - self.hir_id_map - .get(&hir_id) - .copied() - .map_or(false, |hir_id| self.expect_node(location.into()).drop_state.contains(hir_id)) + self.tracked_value_map + .get(&TrackedValue::Temporary(hir_id)) + .or(self.tracked_value_map.get(&TrackedValue::Variable(hir_id))) + .cloned() + .map_or(false, |tracked_value_id| { + self.expect_node(location.into()).drop_state.contains(tracked_value_id) + }) } /// Returns a reference to the NodeInfo for a node, panicking if it does not exist @@ -118,7 +163,7 @@ struct DropRangesBuilder { /// (see NodeInfo::drop_state). The hir_id_map field stores the mapping /// from HirIds to the HirIdIndex that is used to represent that value in /// bitvector. - hir_id_map: HirIdMap, + tracked_value_map: FxHashMap, /// When building the control flow graph, we don't always know the /// post-order index of the target node at the point we encounter it. @@ -138,7 +183,7 @@ struct DropRangesBuilder { impl Debug for DropRangesBuilder { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("DropRanges") - .field("hir_id_map", &self.hir_id_map) + .field("hir_id_map", &self.tracked_value_map) .field("post_order_maps", &self.post_order_map) .field("nodes", &self.nodes.iter_enumerated().collect::>()) .finish() @@ -154,7 +199,7 @@ impl Debug for DropRangesBuilder { impl DropRangesBuilder { /// Returns the number of values (hir_ids) that are tracked fn num_values(&self) -> usize { - self.hir_id_map.len() + self.tracked_value_map.len() } fn node_mut(&mut self, id: PostOrderId) -> &mut NodeInfo { @@ -177,13 +222,13 @@ struct NodeInfo { successors: Vec, /// List of hir_ids that are dropped by this node. - drops: Vec, + drops: Vec, /// List of hir_ids that are reinitialized by this node. - reinits: Vec, + reinits: Vec, /// Set of values that are definitely dropped at this point. - drop_state: BitSet, + drop_state: BitSet, } impl NodeInfo { diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs index 1591b144dc62b..dfe8ed54b2192 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs @@ -1,11 +1,12 @@ use super::{ for_each_consumable, record_consumed_borrow::ConsumedAndBorrowedPlaces, DropRangesBuilder, - HirIdIndex, NodeInfo, PostOrderId, + NodeInfo, PostOrderId, TrackedValue, TrackedValueIndex, }; use hir::{ intravisit::{self, NestedVisitorMap, Visitor}, - Body, Expr, ExprKind, Guard, HirId, HirIdMap, + Body, Expr, ExprKind, Guard, HirId, }; +use rustc_data_structures::fx::FxHashMap; use rustc_hir as hir; use rustc_index::vec::IndexVec; use rustc_middle::{ @@ -61,20 +62,20 @@ impl<'a, 'tcx> DropRangeVisitor<'a, 'tcx> { ) -> Self { debug!("consumed_places: {:?}", places.consumed); let drop_ranges = DropRangesBuilder::new( - places.consumed.iter().flat_map(|(_, places)| places.iter().copied()), + places.consumed.iter().flat_map(|(_, places)| places.iter().cloned()), hir, num_exprs, ); Self { hir, places, drop_ranges, expr_index: PostOrderId::from_u32(0), typeck_results, tcx } } - fn record_drop(&mut self, hir_id: HirId) { - if self.places.borrowed.contains(&hir_id) { - debug!("not marking {:?} as dropped because it is borrowed at some point", hir_id); + fn record_drop(&mut self, value: TrackedValue) { + if self.places.borrowed.contains(&value) { + debug!("not marking {:?} as dropped because it is borrowed at some point", value); } else { - debug!("marking {:?} as dropped at {:?}", hir_id, self.expr_index); + debug!("marking {:?} as dropped at {:?}", value, self.expr_index); let count = self.expr_index; - self.drop_ranges.drop_at(hir_id, count); + self.drop_ranges.drop_at(value, count); } } @@ -88,7 +89,9 @@ impl<'a, 'tcx> DropRangeVisitor<'a, 'tcx> { .get(&expr.hir_id) .map_or(vec![], |places| places.iter().cloned().collect()); for place in places { - for_each_consumable(place, self.hir.find(place), |hir_id| self.record_drop(hir_id)); + for_each_consumable(place, self.hir.find(place.hir_id()), |value| { + self.record_drop(value) + }); } } @@ -100,7 +103,7 @@ impl<'a, 'tcx> DropRangeVisitor<'a, 'tcx> { { let location = self.expr_index; debug!("reinitializing {:?} at {:?}", hir_id, location); - self.drop_ranges.reinit_at(*hir_id, location); + self.drop_ranges.reinit_at(TrackedValue::Variable(*hir_id), location); } else { debug!("reinitializing {:?} is not supported", expr); } @@ -264,36 +267,40 @@ impl<'a, 'tcx> Visitor<'tcx> for DropRangeVisitor<'a, 'tcx> { } impl DropRangesBuilder { - fn new(hir_ids: impl Iterator, hir: Map<'_>, num_exprs: usize) -> Self { - let mut hir_id_map = HirIdMap::::default(); + fn new( + tracked_values: impl Iterator, + hir: Map<'_>, + num_exprs: usize, + ) -> Self { + let mut tracked_value_map = FxHashMap::<_, TrackedValueIndex>::default(); let mut next = <_>::from(0u32); - for hir_id in hir_ids { - for_each_consumable(hir_id, hir.find(hir_id), |hir_id| { - if !hir_id_map.contains_key(&hir_id) { - hir_id_map.insert(hir_id, next); - next = <_>::from(next.index() + 1); + for value in tracked_values { + for_each_consumable(value, hir.find(value.hir_id()), |value| { + if !tracked_value_map.contains_key(&value) { + tracked_value_map.insert(value, next); + next = next + 1; } }); } - debug!("hir_id_map: {:?}", hir_id_map); - let num_values = hir_id_map.len(); + debug!("hir_id_map: {:?}", tracked_value_map); + let num_values = tracked_value_map.len(); Self { - hir_id_map, + tracked_value_map, nodes: IndexVec::from_fn_n(|_| NodeInfo::new(num_values), num_exprs + 1), deferred_edges: <_>::default(), post_order_map: <_>::default(), } } - fn hidx(&self, hir_id: HirId) -> HirIdIndex { - *self.hir_id_map.get(&hir_id).unwrap() + fn tracked_value_index(&self, tracked_value: TrackedValue) -> TrackedValueIndex { + *self.tracked_value_map.get(&tracked_value).unwrap() } /// Adds an entry in the mapping from HirIds to PostOrderIds /// /// Needed so that `add_control_edge_hir_id` can work. - fn add_node_mapping(&mut self, hir_id: HirId, post_order_id: PostOrderId) { - self.post_order_map.insert(hir_id, post_order_id); + fn add_node_mapping(&mut self, node_hir_id: HirId, post_order_id: PostOrderId) { + self.post_order_map.insert(node_hir_id, post_order_id); } /// Like add_control_edge, but uses a hir_id as the target. @@ -304,13 +311,13 @@ impl DropRangesBuilder { self.deferred_edges.push((from, to)); } - fn drop_at(&mut self, value: HirId, location: PostOrderId) { - let value = self.hidx(value); + fn drop_at(&mut self, value: TrackedValue, location: PostOrderId) { + let value = self.tracked_value_index(value); self.node_mut(location.into()).drops.push(value); } - fn reinit_at(&mut self, value: HirId, location: PostOrderId) { - let value = match self.hir_id_map.get(&value) { + fn reinit_at(&mut self, value: TrackedValue, location: PostOrderId) { + let value = match self.tracked_value_map.get(&value) { Some(value) => *value, // If there's no value, this is never consumed and therefore is never dropped. We can // ignore this. diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs index 36e843e7fd1f3..2548b60809281 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs @@ -1,16 +1,14 @@ +use super::TrackedValue; use crate::{ check::FnCtxt, expr_use_visitor::{self, ExprUseVisitor}, }; -use hir::{def_id::DefId, Body, HirId, HirIdMap, HirIdSet}; +use hir::{def_id::DefId, Body, HirId, HirIdMap}; +use rustc_data_structures::stable_set::FxHashSet; use rustc_hir as hir; -use rustc_middle::hir::{ - map::Map, - place::{Place, PlaceBase}, -}; -use rustc_middle::ty; +use rustc_middle::hir::map::Map; -pub fn find_consumed_and_borrowed<'a, 'tcx>( +pub(super) fn find_consumed_and_borrowed<'a, 'tcx>( fcx: &'a FnCtxt<'a, 'tcx>, def_id: DefId, body: &'tcx Body<'tcx>, @@ -20,7 +18,7 @@ pub fn find_consumed_and_borrowed<'a, 'tcx>( expr_use_visitor.places } -pub struct ConsumedAndBorrowedPlaces { +pub(super) struct ConsumedAndBorrowedPlaces { /// Records the variables/expressions that are dropped by a given expression. /// /// The key is the hir-id of the expression, and the value is a set or hir-ids for variables @@ -28,9 +26,9 @@ pub struct ConsumedAndBorrowedPlaces { /// /// Note that this set excludes "partial drops" -- for example, a statement like `drop(x.y)` is /// not considered a drop of `x`, although it would be a drop of `x.y`. - pub consumed: HirIdMap, + pub(super) consumed: HirIdMap>, /// A set of hir-ids of values or variables that are borrowed at some point within the body. - pub borrowed: HirIdSet, + pub(super) borrowed: FxHashSet, } /// Works with ExprUseVisitor to find interesting values for the drop range analysis. @@ -65,7 +63,7 @@ impl<'tcx> ExprUseDelegate<'tcx> { .consume_body(body); } - fn mark_consumed(&mut self, consumer: HirId, target: HirId) { + fn mark_consumed(&mut self, consumer: HirId, target: TrackedValue) { if !self.places.consumed.contains_key(&consumer) { self.places.consumed.insert(consumer, <_>::default()); } @@ -87,8 +85,7 @@ impl<'tcx> expr_use_visitor::Delegate<'tcx> for ExprUseDelegate<'tcx> { "consume {:?}; diag_expr_id={:?}, using parent {:?}", place_with_id, diag_expr_id, parent ); - self.mark_consumed(parent, place_with_id.hir_id); - place_hir_id(&place_with_id.place).map(|place| self.mark_consumed(parent, place)); + self.mark_consumed(parent, place_with_id.into()); } fn borrow( @@ -97,7 +94,7 @@ impl<'tcx> expr_use_visitor::Delegate<'tcx> for ExprUseDelegate<'tcx> { _diag_expr_id: HirId, _bk: rustc_middle::ty::BorrowKind, ) { - place_hir_id(&place_with_id.place).map(|place| self.places.borrowed.insert(place)); + self.places.borrowed.insert(place_with_id.into()); } fn mutate( @@ -115,13 +112,3 @@ impl<'tcx> expr_use_visitor::Delegate<'tcx> for ExprUseDelegate<'tcx> { ) { } } - -/// Gives the hir_id associated with a place if one exists. This is the hir_id that we want to -/// track for a value in the drop range analysis. -fn place_hir_id(place: &Place<'_>) -> Option { - match place.base { - PlaceBase::Rvalue | PlaceBase::StaticItem => None, - PlaceBase::Local(hir_id) - | PlaceBase::Upvar(ty::UpvarId { var_path: ty::UpvarPath { hir_id }, .. }) => Some(hir_id), - } -} From 32930d9ea7cc79239daa19a040cbae9867053af8 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Wed, 5 Jan 2022 14:11:37 -0800 Subject: [PATCH 32/35] Safely handle partial drops We previously weren't tracking partial re-inits while being too aggressive around partial drops. With this change, we simply ignore partial drops, which is the safer, more conservative choice. --- .../drop_ranges/record_consumed_borrow.rs | 6 +++- .../partial-drop-partial-reinit.rs | 29 +++++++++++++++++++ .../partial-drop-partial-reinit.stderr | 27 +++++++++++++++++ src/test/ui/generator/partial-drop.rs | 4 +-- src/test/ui/generator/partial-drop.stderr | 25 ++++++++++++++++ 5 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 src/test/ui/async-await/partial-drop-partial-reinit.rs create mode 100644 src/test/ui/async-await/partial-drop-partial-reinit.stderr create mode 100644 src/test/ui/generator/partial-drop.stderr diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs index 2548b60809281..845cd01a44eed 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs @@ -85,7 +85,11 @@ impl<'tcx> expr_use_visitor::Delegate<'tcx> for ExprUseDelegate<'tcx> { "consume {:?}; diag_expr_id={:?}, using parent {:?}", place_with_id, diag_expr_id, parent ); - self.mark_consumed(parent, place_with_id.into()); + // We do not currently support partial drops or reinits, so just ignore + // any places with projections. + if place_with_id.place.projections.is_empty() { + self.mark_consumed(parent, place_with_id.into()); + } } fn borrow( diff --git a/src/test/ui/async-await/partial-drop-partial-reinit.rs b/src/test/ui/async-await/partial-drop-partial-reinit.rs new file mode 100644 index 0000000000000..73f0ca8153cb9 --- /dev/null +++ b/src/test/ui/async-await/partial-drop-partial-reinit.rs @@ -0,0 +1,29 @@ +// edition:2021 +#![feature(negative_impls)] +#![allow(unused)] + +fn main() { + gimme_send(foo()); + //~^ ERROR cannot be sent between threads safely +} + +fn gimme_send(t: T) { + drop(t); +} + +struct NotSend {} + +impl Drop for NotSend { + fn drop(&mut self) {} +} + +impl !Send for NotSend {} + +async fn foo() { + let mut x = (NotSend {},); + drop(x.0); + x.0 = NotSend {}; + bar().await; +} + +async fn bar() {} diff --git a/src/test/ui/async-await/partial-drop-partial-reinit.stderr b/src/test/ui/async-await/partial-drop-partial-reinit.stderr new file mode 100644 index 0000000000000..2097642eb24ab --- /dev/null +++ b/src/test/ui/async-await/partial-drop-partial-reinit.stderr @@ -0,0 +1,27 @@ +error[E0277]: `NotSend` cannot be sent between threads safely + --> $DIR/partial-drop-partial-reinit.rs:6:16 + | +LL | gimme_send(foo()); + | ---------- ^^^^^ `NotSend` cannot be sent between threads safely + | | + | required by a bound introduced by this call +... +LL | async fn foo() { + | - within this `impl Future` + | + = help: within `impl Future`, the trait `Send` is not implemented for `NotSend` + = note: required because it appears within the type `(NotSend,)` + = note: required because it appears within the type `{ResumeTy, (NotSend,), impl Future, ()}` + = note: required because it appears within the type `[static generator@$DIR/partial-drop-partial-reinit.rs:22:16: 27:2]` + = note: required because it appears within the type `from_generator::GenFuture<[static generator@$DIR/partial-drop-partial-reinit.rs:22:16: 27:2]>` + = note: required because it appears within the type `impl Future` + = note: required because it appears within the type `impl Future` +note: required by a bound in `gimme_send` + --> $DIR/partial-drop-partial-reinit.rs:10:18 + | +LL | fn gimme_send(t: T) { + | ^^^^ required by this bound in `gimme_send` + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/src/test/ui/generator/partial-drop.rs b/src/test/ui/generator/partial-drop.rs index a2f616aa31336..c8c07ba41c7e8 100644 --- a/src/test/ui/generator/partial-drop.rs +++ b/src/test/ui/generator/partial-drop.rs @@ -1,5 +1,3 @@ -// check-pass - #![feature(negative_impls, generators)] struct Foo; @@ -12,6 +10,8 @@ struct Bar { fn main() { assert_send(|| { + //~^ ERROR generator cannot be sent between threads safely + // FIXME: it would be nice to make this work. let guard = Bar { foo: Foo, x: 42 }; drop(guard.foo); yield; diff --git a/src/test/ui/generator/partial-drop.stderr b/src/test/ui/generator/partial-drop.stderr new file mode 100644 index 0000000000000..93112f52208a5 --- /dev/null +++ b/src/test/ui/generator/partial-drop.stderr @@ -0,0 +1,25 @@ +error: generator cannot be sent between threads safely + --> $DIR/partial-drop.rs:12:5 + | +LL | assert_send(|| { + | ^^^^^^^^^^^ generator is not `Send` + | + = help: within `[generator@$DIR/partial-drop.rs:12:17: 18:6]`, the trait `Send` is not implemented for `Foo` +note: generator is not `Send` as this value is used across a yield + --> $DIR/partial-drop.rs:17:9 + | +LL | let guard = Bar { foo: Foo, x: 42 }; + | ----- has type `Bar` which is not `Send` +LL | drop(guard.foo); +LL | yield; + | ^^^^^ yield occurs here, with `guard` maybe used later +LL | }) + | - `guard` is later dropped here +note: required by a bound in `assert_send` + --> $DIR/partial-drop.rs:21:19 + | +LL | fn assert_send(_: T) {} + | ^^^^ required by this bound in `assert_send` + +error: aborting due to previous error + From e0a5370ef00938db0e76f6d7845befb51be629ff Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Fri, 14 Jan 2022 17:45:00 -0800 Subject: [PATCH 33/35] Respond to code review comments --- .../check/generator_interior/drop_ranges.rs | 48 +++-- .../drop_ranges/cfg_build.rs | 167 ++++++++++++++++-- .../drop_ranges/record_consumed_borrow.rs | 12 +- src/test/ui/generator/partial-drop.rs | 21 ++- src/test/ui/generator/partial-drop.stderr | 52 +++++- 5 files changed, 265 insertions(+), 35 deletions(-) diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs index 681cd7cf935f5..21a8d7b563456 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs @@ -21,6 +21,7 @@ use rustc_data_structures::fx::FxHashMap; use rustc_hir as hir; use rustc_index::bit_set::BitSet; use rustc_index::vec::IndexVec; +use rustc_middle::hir::map::Map; use rustc_middle::hir::place::{PlaceBase, PlaceWithHirId}; use rustc_middle::ty; use std::collections::BTreeMap; @@ -53,15 +54,18 @@ pub fn compute_drop_ranges<'a, 'tcx>( DropRanges { tracked_value_map: drop_ranges.tracked_value_map, nodes: drop_ranges.nodes } } -/// Applies `f` to consumable portion of a HIR node. +/// Applies `f` to consumable node in the HIR subtree pointed to by `place`. /// -/// The `node` parameter should be the result of calling `Map::find(place)`. -fn for_each_consumable( - place: TrackedValue, - node: Option>, - mut f: impl FnMut(TrackedValue), -) { +/// This includes the place itself, and if the place is a reference to a local +/// variable then `f` is also called on the HIR node for that variable as well. +/// +/// For example, if `place` points to `foo()`, then `f` is called once for the +/// result of `foo`. On the other hand, if `place` points to `x` then `f` will +/// be called both on the `ExprKind::Path` node that represents the expression +/// as well as the HirId of the local `x` itself. +fn for_each_consumable<'tcx>(hir: Map<'tcx>, place: TrackedValue, mut f: impl FnMut(TrackedValue)) { f(place); + let node = hir.find(place.hir_id()); if let Some(Node::Expr(expr)) = node { match expr.kind { hir::ExprKind::Path(hir::QPath::Resolved( @@ -108,15 +112,37 @@ impl TrackedValue { } } -impl From<&PlaceWithHirId<'_>> for TrackedValue { - fn from(place_with_id: &PlaceWithHirId<'_>) -> Self { +/// Represents a reason why we might not be able to convert a HirId or Place +/// into a tracked value. +#[derive(Debug)] +enum TrackedValueConversionError { + /// Place projects are not currently supported. + /// + /// The reasoning around these is kind of subtle, so we choose to be more + /// conservative around these for now. There is not reason in theory we + /// cannot support these, we just have not implemented it yet. + PlaceProjectionsNotSupported, +} + +impl TryFrom<&PlaceWithHirId<'_>> for TrackedValue { + type Error = TrackedValueConversionError; + + fn try_from(place_with_id: &PlaceWithHirId<'_>) -> Result { + if !place_with_id.place.projections.is_empty() { + debug!( + "TrackedValue from PlaceWithHirId: {:?} has projections, which are not supported.", + place_with_id + ); + return Err(TrackedValueConversionError::PlaceProjectionsNotSupported); + } + match place_with_id.place.base { PlaceBase::Rvalue | PlaceBase::StaticItem => { - TrackedValue::Temporary(place_with_id.hir_id) + Ok(TrackedValue::Temporary(place_with_id.hir_id)) } PlaceBase::Local(hir_id) | PlaceBase::Upvar(ty::UpvarId { var_path: ty::UpvarPath { hir_id }, .. }) => { - TrackedValue::Variable(hir_id) + Ok(TrackedValue::Variable(hir_id)) } } } diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs index dfe8ed54b2192..d7305957f94f8 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs @@ -43,6 +43,41 @@ pub(super) fn build_control_flow_graph<'tcx>( /// We are interested in points where a variables is dropped or initialized, and the control flow /// of the code. We identify locations in code by their post-order traversal index, so it is /// important for this traversal to match that in `RegionResolutionVisitor` and `InteriorVisitor`. +/// +/// We make several simplifying assumptions, with the goal of being more conservative than +/// necessary rather than less conservative (since being less conservative is unsound, but more +/// conservative is still safe). These assumptions are: +/// +/// 1. Moving a variable `a` counts as a move of the whole variable. +/// 2. Moving a partial path like `a.b.c` is ignored. +/// 3. Reinitializing through a field (e.g. `a.b.c = 5`) counds as a reinitialization of all of +/// `a`. +/// +/// Some examples: +/// +/// Rule 1: +/// ```rust +/// let mut a = (vec![0], vec![0]); +/// drop(a); +/// // `a` is not considered initialized. +/// ``` +/// +/// Rule 2: +/// ```rust +/// let mut a = (vec![0], vec![0]); +/// drop(a.0); +/// drop(a.1); +/// // `a` is still considered initialized. +/// ``` +/// +/// Rule 3: +/// ```rust +/// let mut a = (vec![0], vec![0]); +/// drop(a); +/// a.1 = vec![1]; +/// // all of `a` is considered initialized +/// ``` + struct DropRangeVisitor<'a, 'tcx> { hir: Map<'tcx>, places: ConsumedAndBorrowedPlaces, @@ -89,23 +124,76 @@ impl<'a, 'tcx> DropRangeVisitor<'a, 'tcx> { .get(&expr.hir_id) .map_or(vec![], |places| places.iter().cloned().collect()); for place in places { - for_each_consumable(place, self.hir.find(place.hir_id()), |value| { - self.record_drop(value) - }); + for_each_consumable(self.hir, place, |value| self.record_drop(value)); } } + /// Marks an expression as being reinitialized. + /// + /// Note that we always approximated on the side of things being more + /// initialized than they actually are, as opposed to less. In cases such + /// as `x.y = ...`, we would consider all of `x` as being initialized + /// instead of just the `y` field. + /// + /// This is because it is always safe to consider something initialized + /// even when it is not, but the other way around will cause problems. + /// + /// In the future, we will hopefully tighten up these rules to be more + /// precise. fn reinit_expr(&mut self, expr: &hir::Expr<'_>) { - if let ExprKind::Path(hir::QPath::Resolved( - _, - hir::Path { res: hir::def::Res::Local(hir_id), .. }, - )) = expr.kind - { - let location = self.expr_index; - debug!("reinitializing {:?} at {:?}", hir_id, location); - self.drop_ranges.reinit_at(TrackedValue::Variable(*hir_id), location); - } else { - debug!("reinitializing {:?} is not supported", expr); + // Walk the expression to find the base. For example, in an expression + // like `*a[i].x`, we want to find the `a` and mark that as + // reinitialized. + match expr.kind { + ExprKind::Path(hir::QPath::Resolved( + _, + hir::Path { res: hir::def::Res::Local(hir_id), .. }, + )) => { + // This is the base case, where we have found an actual named variable. + + let location = self.expr_index; + debug!("reinitializing {:?} at {:?}", hir_id, location); + self.drop_ranges.reinit_at(TrackedValue::Variable(*hir_id), location); + } + + ExprKind::Field(base, _) => self.reinit_expr(base), + + // Most expressions do not refer to something where we need to track + // reinitializations. + // + // Some of these may be interesting in the future + ExprKind::Path(..) + | ExprKind::Box(_) + | ExprKind::ConstBlock(_) + | ExprKind::Array(_) + | ExprKind::Call(_, _) + | ExprKind::MethodCall(_, _, _, _) + | ExprKind::Tup(_) + | ExprKind::Binary(_, _, _) + | ExprKind::Unary(_, _) + | ExprKind::Lit(_) + | ExprKind::Cast(_, _) + | ExprKind::Type(_, _) + | ExprKind::DropTemps(_) + | ExprKind::Let(_, _, _) + | ExprKind::If(_, _, _) + | ExprKind::Loop(_, _, _, _) + | ExprKind::Match(_, _, _) + | ExprKind::Closure(_, _, _, _, _) + | ExprKind::Block(_, _) + | ExprKind::Assign(_, _, _) + | ExprKind::AssignOp(_, _, _) + | ExprKind::Index(_, _) + | ExprKind::AddrOf(_, _, _) + | ExprKind::Break(_, _) + | ExprKind::Continue(_) + | ExprKind::Ret(_) + | ExprKind::InlineAsm(_) + | ExprKind::LlvmInlineAsm(_) + | ExprKind::Struct(_, _, _) + | ExprKind::Repeat(_, _) + | ExprKind::Yield(_, _) + | ExprKind::Err => (), } } @@ -158,13 +246,47 @@ impl<'a, 'tcx> Visitor<'tcx> for DropRangeVisitor<'a, 'tcx> { self.drop_ranges.add_control_edge(true_end, self.expr_index + 1); } ExprKind::Match(scrutinee, arms, ..) => { + // We walk through the match expression almost like a chain of if expressions. + // Here's a diagram to follow along with: + // + // ┌─┐ + // match │A│ { + // ┌───┴─┘ + // │ + // ┌▼┌───►┌─┐ ┌─┐ + // │B│ if │C│ =>│D│, + // └─┘ ├─┴──►└─┴──────┐ + // ┌──┘ │ + // ┌──┘ │ + // │ │ + // ┌▼┌───►┌─┐ ┌─┐ │ + // │E│ if │F│ =>│G│, │ + // └─┘ ├─┴──►└─┴┐ │ + // │ │ │ + // } ▼ ▼ │ + // ┌─┐◄───────────────────┘ + // │H│ + // └─┘ + // + // The order we want is that the scrutinee (A) flows into the first pattern (B), + // which flows into the guard (C). Then the guard either flows into the arm body + // (D) or into the start of the next arm (E). Finally, the body flows to the end + // of the match block (H). + // + // The subsequent arms follow the same ordering. First we go to the pattern, then + // the guard (if present, otherwise it flows straight into the body), then into + // the body and then to the end of the match expression. + // + // The comments below show which edge is being added. self.visit_expr(scrutinee); let (guard_exit, arm_end_ids) = arms.iter().fold( (self.expr_index, vec![]), |(incoming_edge, mut arm_end_ids), hir::Arm { pat, body, guard, .. }| { + // A -> B, or C -> E self.drop_ranges.add_control_edge(incoming_edge, self.expr_index + 1); self.visit_pat(pat); + // B -> C and E -> F are added implicitly due to the traversal order. match guard { Some(Guard::If(expr)) => self.visit_expr(expr), Some(Guard::IfLet(pat, expr)) => { @@ -173,17 +295,34 @@ impl<'a, 'tcx> Visitor<'tcx> for DropRangeVisitor<'a, 'tcx> { } None => (), } + // Likewise, C -> D and F -> G are added implicitly. + + // Save C, F, so we can add the other outgoing edge. let to_next_arm = self.expr_index; + // The default edge does not get added since we also have an explicit edge, // so we also need to add an edge to the next node as well. + // + // This adds C -> D, F -> G self.drop_ranges.add_control_edge(self.expr_index, self.expr_index + 1); self.visit_expr(body); + + // Save the end of the body so we can add the exit edge once we know where + // the exit is. arm_end_ids.push(self.expr_index); + + // Pass C to the next iteration, as well as vec![D] + // + // On the last round through, we pass F and vec![D, G] so that we can + // add all the exit edges. (to_next_arm, arm_end_ids) }, ); + // F -> H self.drop_ranges.add_control_edge(guard_exit, self.expr_index + 1); + arm_end_ids.into_iter().for_each(|arm_end| { + // D -> H, G -> H self.drop_ranges.add_control_edge(arm_end, self.expr_index + 1) }); } @@ -275,7 +414,7 @@ impl DropRangesBuilder { let mut tracked_value_map = FxHashMap::<_, TrackedValueIndex>::default(); let mut next = <_>::from(0u32); for value in tracked_values { - for_each_consumable(value, hir.find(value.hir_id()), |value| { + for_each_consumable(hir, value, |value| { if !tracked_value_map.contains_key(&value) { tracked_value_map.insert(value, next); next = next + 1; diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs index 845cd01a44eed..059a135a6fb65 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs @@ -85,11 +85,9 @@ impl<'tcx> expr_use_visitor::Delegate<'tcx> for ExprUseDelegate<'tcx> { "consume {:?}; diag_expr_id={:?}, using parent {:?}", place_with_id, diag_expr_id, parent ); - // We do not currently support partial drops or reinits, so just ignore - // any places with projections. - if place_with_id.place.projections.is_empty() { - self.mark_consumed(parent, place_with_id.into()); - } + place_with_id + .try_into() + .map_or((), |tracked_value| self.mark_consumed(parent, tracked_value)); } fn borrow( @@ -98,7 +96,9 @@ impl<'tcx> expr_use_visitor::Delegate<'tcx> for ExprUseDelegate<'tcx> { _diag_expr_id: HirId, _bk: rustc_middle::ty::BorrowKind, ) { - self.places.borrowed.insert(place_with_id.into()); + place_with_id + .try_into() + .map_or(false, |tracked_value| self.places.borrowed.insert(tracked_value)); } fn mutate( diff --git a/src/test/ui/generator/partial-drop.rs b/src/test/ui/generator/partial-drop.rs index c8c07ba41c7e8..36f6e78cb3bfe 100644 --- a/src/test/ui/generator/partial-drop.rs +++ b/src/test/ui/generator/partial-drop.rs @@ -15,7 +15,26 @@ fn main() { let guard = Bar { foo: Foo, x: 42 }; drop(guard.foo); yield; - }) + }); + + assert_send(|| { + //~^ ERROR generator cannot be sent between threads safely + // FIXME: it would be nice to make this work. + let guard = Bar { foo: Foo, x: 42 }; + drop(guard); + guard.foo = Foo; + guard.x = 23; + yield; + }); + + assert_send(|| { + //~^ ERROR generator cannot be sent between threads safely + // FIXME: it would be nice to make this work. + let guard = Bar { foo: Foo, x: 42 }; + let Bar { foo, x } = guard; + drop(foo); + yield; + }); } fn assert_send(_: T) {} diff --git a/src/test/ui/generator/partial-drop.stderr b/src/test/ui/generator/partial-drop.stderr index 93112f52208a5..9a1b0734d8c86 100644 --- a/src/test/ui/generator/partial-drop.stderr +++ b/src/test/ui/generator/partial-drop.stderr @@ -13,13 +13,59 @@ LL | let guard = Bar { foo: Foo, x: 42 }; LL | drop(guard.foo); LL | yield; | ^^^^^ yield occurs here, with `guard` maybe used later -LL | }) +LL | }); | - `guard` is later dropped here note: required by a bound in `assert_send` - --> $DIR/partial-drop.rs:21:19 + --> $DIR/partial-drop.rs:40:19 | LL | fn assert_send(_: T) {} | ^^^^ required by this bound in `assert_send` -error: aborting due to previous error +error: generator cannot be sent between threads safely + --> $DIR/partial-drop.rs:20:5 + | +LL | assert_send(|| { + | ^^^^^^^^^^^ generator is not `Send` + | + = help: within `[generator@$DIR/partial-drop.rs:20:17: 28:6]`, the trait `Send` is not implemented for `Foo` +note: generator is not `Send` as this value is used across a yield + --> $DIR/partial-drop.rs:27:9 + | +LL | let guard = Bar { foo: Foo, x: 42 }; + | ----- has type `Bar` which is not `Send` +... +LL | yield; + | ^^^^^ yield occurs here, with `guard` maybe used later +LL | }); + | - `guard` is later dropped here +note: required by a bound in `assert_send` + --> $DIR/partial-drop.rs:40:19 + | +LL | fn assert_send(_: T) {} + | ^^^^ required by this bound in `assert_send` + +error: generator cannot be sent between threads safely + --> $DIR/partial-drop.rs:30:5 + | +LL | assert_send(|| { + | ^^^^^^^^^^^ generator is not `Send` + | + = help: within `[generator@$DIR/partial-drop.rs:30:17: 37:6]`, the trait `Send` is not implemented for `Foo` +note: generator is not `Send` as this value is used across a yield + --> $DIR/partial-drop.rs:36:9 + | +LL | let guard = Bar { foo: Foo, x: 42 }; + | ----- has type `Bar` which is not `Send` +... +LL | yield; + | ^^^^^ yield occurs here, with `guard` maybe used later +LL | }); + | - `guard` is later dropped here +note: required by a bound in `assert_send` + --> $DIR/partial-drop.rs:40:19 + | +LL | fn assert_send(_: T) {} + | ^^^^ required by this bound in `assert_send` + +error: aborting due to 3 previous errors From d840d0c62e3853f9f3569315ffff666c4452718a Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Tue, 18 Jan 2022 14:02:42 -0800 Subject: [PATCH 34/35] Use .. patterns in cfg_build.rs --- .../drop_ranges/cfg_build.rs | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs index d7305957f94f8..fc150e09484b6 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs @@ -163,36 +163,36 @@ impl<'a, 'tcx> DropRangeVisitor<'a, 'tcx> { // // Some of these may be interesting in the future ExprKind::Path(..) - | ExprKind::Box(_) - | ExprKind::ConstBlock(_) - | ExprKind::Array(_) - | ExprKind::Call(_, _) - | ExprKind::MethodCall(_, _, _, _) - | ExprKind::Tup(_) - | ExprKind::Binary(_, _, _) - | ExprKind::Unary(_, _) - | ExprKind::Lit(_) - | ExprKind::Cast(_, _) - | ExprKind::Type(_, _) - | ExprKind::DropTemps(_) - | ExprKind::Let(_, _, _) - | ExprKind::If(_, _, _) - | ExprKind::Loop(_, _, _, _) - | ExprKind::Match(_, _, _) - | ExprKind::Closure(_, _, _, _, _) - | ExprKind::Block(_, _) - | ExprKind::Assign(_, _, _) - | ExprKind::AssignOp(_, _, _) - | ExprKind::Index(_, _) - | ExprKind::AddrOf(_, _, _) - | ExprKind::Break(_, _) - | ExprKind::Continue(_) - | ExprKind::Ret(_) - | ExprKind::InlineAsm(_) - | ExprKind::LlvmInlineAsm(_) - | ExprKind::Struct(_, _, _) - | ExprKind::Repeat(_, _) - | ExprKind::Yield(_, _) + | ExprKind::Box(..) + | ExprKind::ConstBlock(..) + | ExprKind::Array(..) + | ExprKind::Call(..) + | ExprKind::MethodCall(..) + | ExprKind::Tup(..) + | ExprKind::Binary(..) + | ExprKind::Unary(..) + | ExprKind::Lit(..) + | ExprKind::Cast(..) + | ExprKind::Type(..) + | ExprKind::DropTemps(..) + | ExprKind::Let(..) + | ExprKind::If(..) + | ExprKind::Loop(..) + | ExprKind::Match(..) + | ExprKind::Closure(..) + | ExprKind::Block(..) + | ExprKind::Assign(..) + | ExprKind::AssignOp(..) + | ExprKind::Index(..) + | ExprKind::AddrOf(..) + | ExprKind::Break(..) + | ExprKind::Continue(..) + | ExprKind::Ret(..) + | ExprKind::InlineAsm(..) + | ExprKind::LlvmInlineAsm(..) + | ExprKind::Struct(..) + | ExprKind::Repeat(..) + | ExprKind::Yield(..) | ExprKind::Err => (), } } From 76f6b57125bd9394ad3ce8c241d2eebc4f99042b Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Tue, 18 Jan 2022 14:42:39 -0800 Subject: [PATCH 35/35] Fix build after rebase --- .../check/generator_interior/drop_ranges/cfg_build.rs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs index fc150e09484b6..fc957b899909d 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs @@ -3,7 +3,7 @@ use super::{ NodeInfo, PostOrderId, TrackedValue, TrackedValueIndex, }; use hir::{ - intravisit::{self, NestedVisitorMap, Visitor}, + intravisit::{self, Visitor}, Body, Expr, ExprKind, Guard, HirId, }; use rustc_data_structures::fx::FxHashMap; @@ -189,7 +189,6 @@ impl<'a, 'tcx> DropRangeVisitor<'a, 'tcx> { | ExprKind::Continue(..) | ExprKind::Ret(..) | ExprKind::InlineAsm(..) - | ExprKind::LlvmInlineAsm(..) | ExprKind::Struct(..) | ExprKind::Repeat(..) | ExprKind::Yield(..) @@ -213,12 +212,6 @@ impl<'a, 'tcx> DropRangeVisitor<'a, 'tcx> { } impl<'a, 'tcx> Visitor<'tcx> for DropRangeVisitor<'a, 'tcx> { - type Map = intravisit::ErasedMap<'tcx>; - - fn nested_visit_map(&mut self) -> NestedVisitorMap { - NestedVisitorMap::None - } - fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { let mut reinit = None; match expr.kind { @@ -378,7 +371,6 @@ impl<'a, 'tcx> Visitor<'tcx> for DropRangeVisitor<'a, 'tcx> { | ExprKind::InlineAsm(..) | ExprKind::Let(..) | ExprKind::Lit(..) - | ExprKind::LlvmInlineAsm(..) | ExprKind::Path(..) | ExprKind::Repeat(..) | ExprKind::Ret(..)