diff --git a/src/librustc/ich/impls_mir.rs b/src/librustc/ich/impls_mir.rs index 656f5795e18..9312349ee40 100644 --- a/src/librustc/ich/impls_mir.rs +++ b/src/librustc/ich/impls_mir.rs @@ -29,6 +29,7 @@ impl_stable_hash_for!(struct mir::LocalDecl<'tcx> { source_info, visibility_scope, internal, + is_block_tail, is_user_variable }); impl_stable_hash_for!(struct mir::UpvarDecl { debug_name, var_hir_id, by_ref, mutability }); diff --git a/src/librustc/mir/mod.rs b/src/librustc/mir/mod.rs index aec6bb7c3c1..5ea097e7243 100644 --- a/src/librustc/mir/mod.rs +++ b/src/librustc/mir/mod.rs @@ -638,6 +638,18 @@ mod binding_form_impl { } } +#[derive(Clone, Debug, RustcEncodable, RustcDecodable)] +pub struct BlockTailInfo { + /// If `true`, then the value resulting from evaluating this tail + /// expression is ignored by the block's expression context. + /// + /// Examples include `{ ...; tail };` and `let _ = { ...; tail };` + /// but not e.g. `let _x = { ...; tail };` + pub tail_result_is_ignored: bool, +} + +impl_stable_hash_for!(struct BlockTailInfo { tail_result_is_ignored }); + /// A MIR local. /// /// This can be a binding declared by the user, a temporary inserted by the compiler, a function @@ -677,6 +689,12 @@ pub struct LocalDecl<'tcx> { /// generator. pub internal: bool, + /// If this local is a temporary and `is_block_tail` is `Some`, + /// then it is a temporary created for evaluation of some + /// subexpression of some block's tail expression (with no + /// intervening statement context). + pub is_block_tail: Option, + /// Type of this local. pub ty: Ty<'tcx>, @@ -825,10 +843,19 @@ impl<'tcx> LocalDecl<'tcx> { Self::new_local(ty, Mutability::Mut, false, span) } - /// Create a new immutable `LocalDecl` for a temporary. + /// Converts `self` into same `LocalDecl` except tagged as immutable. #[inline] - pub fn new_immutable_temp(ty: Ty<'tcx>, span: Span) -> Self { - Self::new_local(ty, Mutability::Not, false, span) + pub fn immutable(mut self) -> Self { + self.mutability = Mutability::Not; + self + } + + /// Converts `self` into same `LocalDecl` except tagged as internal temporary. + #[inline] + pub fn block_tail(mut self, info: BlockTailInfo) -> Self { + assert!(self.is_block_tail.is_none()); + self.is_block_tail = Some(info); + self } /// Create a new `LocalDecl` for a internal temporary. @@ -856,6 +883,7 @@ impl<'tcx> LocalDecl<'tcx> { visibility_scope: OUTERMOST_SOURCE_SCOPE, internal, is_user_variable: None, + is_block_tail: None, } } @@ -874,6 +902,7 @@ impl<'tcx> LocalDecl<'tcx> { }, visibility_scope: OUTERMOST_SOURCE_SCOPE, internal: false, + is_block_tail: None, name: None, // FIXME maybe we do want some name here? is_user_variable: None, } @@ -2668,6 +2697,7 @@ pub enum ClosureOutlivesSubject<'tcx> { */ CloneTypeFoldableAndLiftImpls! { + BlockTailInfo, Mutability, SourceInfo, UpvarDecl, @@ -2711,6 +2741,7 @@ BraceStructTypeFoldableImpl! { user_ty, name, source_info, + is_block_tail, visibility_scope, } } diff --git a/src/librustc/mir/visit.rs b/src/librustc/mir/visit.rs index cbfbed90c90..7d8227053b3 100644 --- a/src/librustc/mir/visit.rs +++ b/src/librustc/mir/visit.rs @@ -728,6 +728,7 @@ macro_rules! make_mir_visitor { ref $($mutability)* visibility_scope, internal: _, is_user_variable: _, + is_block_tail: _, } = *local_decl; self.visit_ty(ty, TyContext::LocalDecl { diff --git a/src/librustc_mir/build/block.rs b/src/librustc_mir/build/block.rs index c4cb7958fd3..b754d63f718 100644 --- a/src/librustc_mir/build/block.rs +++ b/src/librustc_mir/build/block.rs @@ -8,7 +8,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use build::{BlockAnd, BlockAndExtension, Builder}; +use build::{BlockAnd, BlockAndExtension, BlockFrame, Builder}; use build::ForGuard::OutsideGuard; use build::matches::ArmHasGuard; use hair::*; @@ -93,6 +93,7 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> { let Stmt { kind, opt_destruction_scope } = this.hir.mirror(stmt); match kind { StmtKind::Expr { scope, expr } => { + this.block_context.push(BlockFrame::Statement { ignores_expr_result: true }); unpack!(block = this.in_opt_scope( opt_destruction_scope.map(|de|(de, source_info)), block, |this| { let si = (scope, source_info); @@ -109,6 +110,13 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> { initializer, lint_level } => { + let ignores_expr_result = if let PatternKind::Wild = *pattern.kind { + true + } else { + false + }; + this.block_context.push(BlockFrame::Statement { ignores_expr_result }); + // Enter the remainder scope, i.e. the bindings' destruction scope. this.push_scope((remainder_scope, source_info)); let_scope_stack.push(remainder_scope); @@ -155,19 +163,40 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> { } } } + + let popped = this.block_context.pop(); + assert!(popped.map_or(false, |bf|bf.is_statement())); } + // Then, the block may have an optional trailing expression which is a “return” value - // of the block. + // of the block, which is stored into `destination`. + let tcx = this.hir.tcx(); + let destination_ty = destination.ty(&this.local_decls, tcx).to_ty(tcx); if let Some(expr) = expr { + let tail_result_is_ignored = destination_ty.is_unit() || + match this.block_context.last() { + // no context: conservatively assume result is read + None => false, + + // sub-expression: block result feeds into some computation + Some(BlockFrame::SubExpr) => false, + + // otherwise: use accumualated is_ignored state. + Some(BlockFrame::TailExpr { tail_result_is_ignored: ignored }) | + Some(BlockFrame::Statement { ignores_expr_result: ignored }) => *ignored, + }; + this.block_context.push(BlockFrame::TailExpr { tail_result_is_ignored }); + unpack!(block = this.into(destination, block, expr)); + let popped = this.block_context.pop(); + + assert!(popped.map_or(false, |bf|bf.is_tail_expr())); } else { // If a block has no trailing expression, then it is given an implicit return type. // This return type is usually `()`, unless the block is diverging, in which case the // return type is `!`. For the unit type, we need to actually return the unit, but in // the case of `!`, no return value is required, as the block will never return. - let tcx = this.hir.tcx(); - let ty = destination.ty(&this.local_decls, tcx).to_ty(tcx); - if ty.is_unit() { + if destination_ty.is_unit() { // We only want to assign an implicit `()` as the return value of the block if the // block does not diverge. (Otherwise, we may try to assign a unit to a `!`-type.) this.cfg.push_assign_unit(block, source_info, destination); diff --git a/src/librustc_mir/build/expr/as_temp.rs b/src/librustc_mir/build/expr/as_temp.rs index a2dcce6adcb..e0bf02c6739 100644 --- a/src/librustc_mir/build/expr/as_temp.rs +++ b/src/librustc_mir/build/expr/as_temp.rs @@ -10,7 +10,7 @@ //! See docs in build/expr/mod.rs -use build::{BlockAnd, BlockAndExtension, Builder}; +use build::{BlockAnd, BlockAndExtension, BlockFrame, Builder}; use hair::*; use rustc::middle::region; use rustc::mir::*; @@ -59,14 +59,30 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> { } let expr_ty = expr.ty; - let temp = if mutability == Mutability::Not { - this.local_decls - .push(LocalDecl::new_immutable_temp(expr_ty, expr_span)) - } else { - this.local_decls - .push(LocalDecl::new_temp(expr_ty, expr_span)) - }; + let temp = { + let mut local_decl = LocalDecl::new_temp(expr_ty, expr_span); + if mutability == Mutability::Not { + local_decl = local_decl.immutable(); + } + debug!("creating temp {:?} with block_context: {:?}", local_decl, this.block_context); + // Find out whether this temp is being created within the + // tail expression of a block whose result is ignored. + for bf in this.block_context.iter().rev() { + match bf { + BlockFrame::SubExpr => continue, + BlockFrame::Statement { .. } => break, + &BlockFrame::TailExpr { tail_result_is_ignored } => { + local_decl = local_decl.block_tail(BlockTailInfo { + tail_result_is_ignored + }); + break; + } + } + } + + this.local_decls.push(local_decl) + }; if !expr_ty.is_never() { this.cfg.push( block, diff --git a/src/librustc_mir/build/expr/into.rs b/src/librustc_mir/build/expr/into.rs index f85e37a6ca5..4f5ed34a461 100644 --- a/src/librustc_mir/build/expr/into.rs +++ b/src/librustc_mir/build/expr/into.rs @@ -11,7 +11,7 @@ //! See docs in build/expr/mod.rs use build::expr::category::{Category, RvalueFunc}; -use build::{BlockAnd, BlockAndExtension, Builder}; +use build::{BlockAnd, BlockAndExtension, BlockFrame, Builder}; use hair::*; use rustc::mir::*; use rustc::ty; @@ -39,7 +39,17 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> { let expr_span = expr.span; let source_info = this.source_info(expr_span); - match expr.kind { + let expr_is_block_or_scope = match expr.kind { + ExprKind::Block { .. } => true, + ExprKind::Scope { .. } => true, + _ => false, + }; + + if !expr_is_block_or_scope { + this.block_context.push(BlockFrame::SubExpr); + } + + let block_and = match expr.kind { ExprKind::Scope { region_scope, lint_level, @@ -302,6 +312,7 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> { visibility_scope: source_info.scope, internal: true, is_user_variable: None, + is_block_tail: None, }); let ptr_temp = Place::Local(ptr_temp); let block = unpack!(this.into(&ptr_temp, block, ptr)); @@ -414,6 +425,13 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> { .push_assign(block, source_info, destination, rvalue); block.unit() } + }; + + if !expr_is_block_or_scope { + let popped = this.block_context.pop(); + assert!(popped.is_some()); } + + block_and } } diff --git a/src/librustc_mir/build/expr/stmt.rs b/src/librustc_mir/build/expr/stmt.rs index 32f09599ace..8cb2b6384b8 100644 --- a/src/librustc_mir/build/expr/stmt.rs +++ b/src/librustc_mir/build/expr/stmt.rs @@ -9,7 +9,7 @@ // except according to those terms. use build::scope::BreakableScope; -use build::{BlockAnd, BlockAndExtension, Builder}; +use build::{BlockAnd, BlockAndExtension, BlockFrame, Builder}; use hair::*; use rustc::mir::*; @@ -40,19 +40,22 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> { // is better for borrowck interaction with overloaded // operators like x[j] = x[i]. + this.block_context.push(BlockFrame::SubExpr); + // Generate better code for things that don't need to be // dropped. if this.hir.needs_drop(lhs.ty) { let rhs = unpack!(block = this.as_local_operand(block, rhs)); let lhs = unpack!(block = this.as_place(block, lhs)); unpack!(block = this.build_drop_and_replace(block, lhs_span, lhs, rhs)); - block.unit() } else { let rhs = unpack!(block = this.as_local_rvalue(block, rhs)); let lhs = unpack!(block = this.as_place(block, lhs)); this.cfg.push_assign(block, source_info, &lhs, rhs); - block.unit() } + + this.block_context.pop(); + block.unit() } ExprKind::AssignOp { op, lhs, rhs } => { // FIXME(#28160) there is an interesting semantics @@ -66,6 +69,8 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> { let lhs = this.hir.mirror(lhs); let lhs_ty = lhs.ty; + this.block_context.push(BlockFrame::SubExpr); + // As above, RTL. let rhs = unpack!(block = this.as_local_operand(block, rhs)); let lhs = unpack!(block = this.as_place(block, lhs)); @@ -85,6 +90,7 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> { ); this.cfg.push_assign(block, source_info, &lhs, result); + this.block_context.pop(); block.unit() } ExprKind::Continue { label } => { @@ -114,7 +120,9 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> { (break_block, region_scope, break_destination.clone()) }; if let Some(value) = value { - unpack!(block = this.into(&destination, block, value)) + this.block_context.push(BlockFrame::SubExpr); + unpack!(block = this.into(&destination, block, value)); + this.block_context.pop(); } else { this.cfg.push_assign_unit(block, source_info, &destination) } @@ -123,7 +131,12 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> { } ExprKind::Return { value } => { block = match value { - Some(value) => unpack!(this.into(&Place::Local(RETURN_PLACE), block, value)), + Some(value) => { + this.block_context.push(BlockFrame::SubExpr); + let result = unpack!(this.into(&Place::Local(RETURN_PLACE), block, value)); + this.block_context.pop(); + result + } None => { this.cfg .push_assign_unit(block, source_info, &Place::Local(RETURN_PLACE)); @@ -140,6 +153,7 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> { outputs, inputs, } => { + this.block_context.push(BlockFrame::SubExpr); let outputs = outputs .into_iter() .map(|output| unpack!(block = this.as_place(block, output))) @@ -161,6 +175,7 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> { }, }, ); + this.block_context.pop(); block.unit() } _ => { diff --git a/src/librustc_mir/build/matches/mod.rs b/src/librustc_mir/build/matches/mod.rs index e40ed51f7d3..656c78a46ed 100644 --- a/src/librustc_mir/build/matches/mod.rs +++ b/src/librustc_mir/build/matches/mod.rs @@ -1485,6 +1485,7 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> { source_info, visibility_scope, internal: false, + is_block_tail: None, is_user_variable: Some(ClearCrossCrate::Set(BindingForm::Var(VarBindingForm { binding_mode, // hypothetically, `visit_bindings` could try to unzip @@ -1518,6 +1519,7 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> { visibility_scope, // FIXME: should these secretly injected ref_for_guard's be marked as `internal`? internal: false, + is_block_tail: None, is_user_variable: Some(ClearCrossCrate::Set(BindingForm::RefForGuard)), }); LocalsForNode::ForGuard { diff --git a/src/librustc_mir/build/mod.rs b/src/librustc_mir/build/mod.rs index 3dbd3bbb415..9e78932bffe 100644 --- a/src/librustc_mir/build/mod.rs +++ b/src/librustc_mir/build/mod.rs @@ -281,6 +281,57 @@ fn liberated_closure_env_ty<'a, 'gcx, 'tcx>(tcx: TyCtxt<'a, 'gcx, 'tcx>, tcx.liberate_late_bound_regions(closure_def_id, &closure_env_ty) } +#[derive(Debug, PartialEq, Eq)] +pub enum BlockFrame { + /// Evaluation is currently within a statement. + /// + /// Examples include: + /// 1. `EXPR;` + /// 2. `let _ = EXPR;` + /// 3. `let x = EXPR;` + Statement { + /// If true, then statement discards result from evaluating + /// the expression (such as examples 1 and 2 above). + ignores_expr_result: bool + }, + + /// Evaluation is currently within the tail expression of a block. + /// + /// Example: `{ STMT_1; STMT_2; EXPR }` + TailExpr { + /// If true, then the surrounding context of the block ignores + /// the result of evaluating the block's tail expression. + /// + /// Example: `let _ = { STMT_1; EXPR };` + tail_result_is_ignored: bool + }, + + /// Generic mark meaning that the block occurred as a subexpression + /// where the result might be used. + /// + /// Examples: `foo(EXPR)`, `match EXPR { ... }` + SubExpr, +} + +impl BlockFrame { + fn is_tail_expr(&self) -> bool { + match *self { + BlockFrame::TailExpr { .. } => true, + + BlockFrame::Statement { .. } | + BlockFrame::SubExpr => false, + } + } + fn is_statement(&self) -> bool { + match *self { + BlockFrame::Statement { .. } => true, + + BlockFrame::TailExpr { .. } | + BlockFrame::SubExpr => false, + } + } + } + struct Builder<'a, 'gcx: 'a+'tcx, 'tcx: 'a> { hir: Cx<'a, 'gcx, 'tcx>, cfg: CFG<'tcx>, @@ -292,6 +343,20 @@ struct Builder<'a, 'gcx: 'a+'tcx, 'tcx: 'a> { /// see the `scope` module for more details scopes: Vec>, + /// the block-context: each time we build the code within an hair::Block, + /// we push a frame here tracking whether we are building a statement or + /// if we are pushing the tail expression of the block. This is used to + /// embed information in generated temps about whether they were created + /// for a block tail expression or not. + /// + /// It would be great if we could fold this into `self.scopes` + /// somehow; but right now I think that is very tightly tied to + /// the code generation in ways that we cannot (or should not) + /// start just throwing new entries onto that vector in order to + /// distinguish the context of EXPR1 from the context of EXPR2 in + /// `{ STMTS; EXPR1 } + EXPR2` + block_context: Vec, + /// The current unsafe block in scope, even if it is hidden by /// a PushUnsafeBlock unpushed_unsafe: Safety, @@ -695,6 +760,7 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> { fn_span: span, arg_count, scopes: vec![], + block_context: vec![], source_scopes: IndexVec::new(), source_scope: OUTERMOST_SOURCE_SCOPE, source_scope_local_data: IndexVec::new(), @@ -781,6 +847,7 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> { name, internal: false, is_user_variable: None, + is_block_tail: None, }); } diff --git a/src/librustc_mir/shim.rs b/src/librustc_mir/shim.rs index 6c3dd0ea3cc..4c9d29bb6d0 100644 --- a/src/librustc_mir/shim.rs +++ b/src/librustc_mir/shim.rs @@ -147,6 +147,7 @@ fn temp_decl(mutability: Mutability, ty: Ty, span: Span) -> LocalDecl { visibility_scope: source_info.scope, internal: false, is_user_variable: None, + is_block_tail: None, } } diff --git a/src/librustc_mir/transform/generator.rs b/src/librustc_mir/transform/generator.rs index 62adbf1bdf7..c2ae6832cc0 100644 --- a/src/librustc_mir/transform/generator.rs +++ b/src/librustc_mir/transform/generator.rs @@ -308,6 +308,7 @@ fn replace_result_variable<'tcx>( source_info, visibility_scope: source_info.scope, internal: false, + is_block_tail: None, is_user_variable: None, }; let new_ret_local = Local::new(mir.local_decls.len()); @@ -662,6 +663,7 @@ fn create_generator_drop_shim<'a, 'tcx>( source_info, visibility_scope: source_info.scope, internal: false, + is_block_tail: None, is_user_variable: None, }; @@ -679,6 +681,7 @@ fn create_generator_drop_shim<'a, 'tcx>( source_info, visibility_scope: source_info.scope, internal: false, + is_block_tail: None, is_user_variable: None, };