diff options
Diffstat (limited to '')
-rw-r--r-- | compiler/rustc_mir_build/src/build/expr/into.rs | 599 |
1 files changed, 599 insertions, 0 deletions
diff --git a/compiler/rustc_mir_build/src/build/expr/into.rs b/compiler/rustc_mir_build/src/build/expr/into.rs new file mode 100644 index 000000000..017d43d10 --- /dev/null +++ b/compiler/rustc_mir_build/src/build/expr/into.rs @@ -0,0 +1,599 @@ +//! See docs in build/expr/mod.rs + +use crate::build::expr::category::{Category, RvalueFunc}; +use crate::build::{BlockAnd, BlockAndExtension, BlockFrame, Builder, NeedsTemporary}; +use rustc_ast::InlineAsmOptions; +use rustc_data_structures::fx::FxHashMap; +use rustc_data_structures::stack::ensure_sufficient_stack; +use rustc_hir as hir; +use rustc_index::vec::Idx; +use rustc_middle::mir::*; +use rustc_middle::thir::*; +use rustc_middle::ty::CanonicalUserTypeAnnotation; +use std::iter; + +impl<'a, 'tcx> Builder<'a, 'tcx> { + /// Compile `expr`, storing the result into `destination`, which + /// is assumed to be uninitialized. + pub(crate) fn expr_into_dest( + &mut self, + destination: Place<'tcx>, + mut block: BasicBlock, + expr: &Expr<'tcx>, + ) -> BlockAnd<()> { + debug!("expr_into_dest(destination={:?}, block={:?}, expr={:?})", destination, block, expr); + + // since we frequently have to reference `self` from within a + // closure, where `self` would be shadowed, it's easier to + // just use the name `this` uniformly + let this = self; + let expr_span = expr.span; + let source_info = this.source_info(expr_span); + + let expr_is_block_or_scope = + matches!(expr.kind, ExprKind::Block { .. } | ExprKind::Scope { .. }); + + if !expr_is_block_or_scope { + this.block_context.push(BlockFrame::SubExpr); + } + + let block_and = match expr.kind { + ExprKind::Scope { region_scope, lint_level, value } => { + let region_scope = (region_scope, source_info); + ensure_sufficient_stack(|| { + this.in_scope(region_scope, lint_level, |this| { + this.expr_into_dest(destination, block, &this.thir[value]) + }) + }) + } + ExprKind::Block { body: ref ast_block } => { + this.ast_block(destination, block, ast_block, source_info) + } + ExprKind::Match { scrutinee, ref arms } => { + this.match_expr(destination, expr_span, block, &this.thir[scrutinee], arms) + } + ExprKind::If { cond, then, else_opt, if_then_scope } => { + let then_blk; + let then_expr = &this.thir[then]; + let then_source_info = this.source_info(then_expr.span); + let condition_scope = this.local_scope(); + + let mut else_blk = unpack!( + then_blk = this.in_scope( + (if_then_scope, then_source_info), + LintLevel::Inherited, + |this| { + let source_info = if this.is_let(cond) { + let variable_scope = this.new_source_scope( + then_expr.span, + LintLevel::Inherited, + None, + ); + this.source_scope = variable_scope; + SourceInfo { span: then_expr.span, scope: variable_scope } + } else { + this.source_info(then_expr.span) + }; + let (then_block, else_block) = + this.in_if_then_scope(condition_scope, |this| { + let then_blk = unpack!(this.then_else_break( + block, + &this.thir[cond], + Some(condition_scope), + condition_scope, + source_info + )); + + this.expr_into_dest(destination, then_blk, then_expr) + }); + then_block.and(else_block) + }, + ) + ); + + else_blk = if let Some(else_opt) = else_opt { + unpack!(this.expr_into_dest(destination, else_blk, &this.thir[else_opt])) + } else { + // Body of the `if` expression without an `else` clause must return `()`, thus + // we implicitly generate an `else {}` if it is not specified. + let correct_si = this.source_info(expr_span.shrink_to_hi()); + this.cfg.push_assign_unit(else_blk, correct_si, destination, this.tcx); + else_blk + }; + + let join_block = this.cfg.start_new_block(); + this.cfg.goto(then_blk, source_info, join_block); + this.cfg.goto(else_blk, source_info, join_block); + join_block.unit() + } + ExprKind::Let { expr, ref pat } => { + let scope = this.local_scope(); + let (true_block, false_block) = this.in_if_then_scope(scope, |this| { + this.lower_let_expr(block, &this.thir[expr], pat, scope, None, expr_span) + }); + + this.cfg.push_assign_constant( + true_block, + source_info, + destination, + Constant { + span: expr_span, + user_ty: None, + literal: ConstantKind::from_bool(this.tcx, true), + }, + ); + + this.cfg.push_assign_constant( + false_block, + source_info, + destination, + Constant { + span: expr_span, + user_ty: None, + literal: ConstantKind::from_bool(this.tcx, false), + }, + ); + + let join_block = this.cfg.start_new_block(); + this.cfg.goto(true_block, source_info, join_block); + this.cfg.goto(false_block, source_info, join_block); + join_block.unit() + } + ExprKind::NeverToAny { source } => { + let source = &this.thir[source]; + let is_call = + matches!(source.kind, ExprKind::Call { .. } | ExprKind::InlineAsm { .. }); + + // (#66975) Source could be a const of type `!`, so has to + // exist in the generated MIR. + unpack!( + block = this.as_temp(block, Some(this.local_scope()), source, Mutability::Mut,) + ); + + // This is an optimization. If the expression was a call then we already have an + // unreachable block. Don't bother to terminate it and create a new one. + if is_call { + block.unit() + } else { + this.cfg.terminate(block, source_info, TerminatorKind::Unreachable); + let end_block = this.cfg.start_new_block(); + end_block.unit() + } + } + ExprKind::LogicalOp { op, lhs, rhs } => { + // And: + // + // [block: If(lhs)] -true-> [else_block: dest = (rhs)] + // | (false) + // [shortcurcuit_block: dest = false] + // + // Or: + // + // [block: If(lhs)] -false-> [else_block: dest = (rhs)] + // | (true) + // [shortcurcuit_block: dest = true] + + let (shortcircuit_block, mut else_block, join_block) = ( + this.cfg.start_new_block(), + this.cfg.start_new_block(), + this.cfg.start_new_block(), + ); + + let lhs = unpack!(block = this.as_local_operand(block, &this.thir[lhs])); + let blocks = match op { + LogicalOp::And => (else_block, shortcircuit_block), + LogicalOp::Or => (shortcircuit_block, else_block), + }; + let term = TerminatorKind::if_(this.tcx, lhs, blocks.0, blocks.1); + this.cfg.terminate(block, source_info, term); + + this.cfg.push_assign_constant( + shortcircuit_block, + source_info, + destination, + Constant { + span: expr_span, + user_ty: None, + literal: match op { + LogicalOp::And => ConstantKind::from_bool(this.tcx, false), + LogicalOp::Or => ConstantKind::from_bool(this.tcx, true), + }, + }, + ); + this.cfg.goto(shortcircuit_block, source_info, join_block); + + let rhs = unpack!(else_block = this.as_local_operand(else_block, &this.thir[rhs])); + this.cfg.push_assign(else_block, source_info, destination, Rvalue::Use(rhs)); + this.cfg.goto(else_block, source_info, join_block); + + join_block.unit() + } + ExprKind::Loop { body } => { + // [block] + // | + // [loop_block] -> [body_block] -/eval. body/-> [body_block_end] + // | ^ | + // false link | | + // | +-----------------------------------------+ + // +-> [diverge_cleanup] + // The false link is required to make sure borrowck considers unwinds through the + // body, even when the exact code in the body cannot unwind + + let loop_block = this.cfg.start_new_block(); + + // Start the loop. + this.cfg.goto(block, source_info, loop_block); + + this.in_breakable_scope(Some(loop_block), destination, expr_span, move |this| { + // conduct the test, if necessary + let body_block = this.cfg.start_new_block(); + this.cfg.terminate( + loop_block, + source_info, + TerminatorKind::FalseUnwind { real_target: body_block, unwind: None }, + ); + this.diverge_from(loop_block); + + // The “return” value of the loop body must always be a unit. We therefore + // introduce a unit temporary as the destination for the loop body. + let tmp = this.get_unit_temp(); + // Execute the body, branching back to the test. + let body_block_end = + unpack!(this.expr_into_dest(tmp, body_block, &this.thir[body])); + this.cfg.goto(body_block_end, source_info, loop_block); + + // Loops are only exited by `break` expressions. + None + }) + } + ExprKind::Call { ty: _, fun, ref args, from_hir_call, fn_span } => { + let fun = unpack!(block = this.as_local_operand(block, &this.thir[fun])); + let args: Vec<_> = args + .into_iter() + .copied() + .map(|arg| unpack!(block = this.as_local_call_operand(block, &this.thir[arg]))) + .collect(); + + let success = this.cfg.start_new_block(); + + this.record_operands_moved(&args); + + debug!("expr_into_dest: fn_span={:?}", fn_span); + + this.cfg.terminate( + block, + source_info, + TerminatorKind::Call { + func: fun, + args, + cleanup: None, + destination, + // The presence or absence of a return edge affects control-flow sensitive + // MIR checks and ultimately whether code is accepted or not. We can only + // omit the return edge if a return type is visibly uninhabited to a module + // that makes the call. + target: if this.tcx.is_ty_uninhabited_from( + this.parent_module, + expr.ty, + this.param_env, + ) { + None + } else { + Some(success) + }, + from_hir_call, + fn_span, + }, + ); + this.diverge_from(block); + success.unit() + } + ExprKind::Use { source } => this.expr_into_dest(destination, block, &this.thir[source]), + ExprKind::Borrow { arg, borrow_kind } => { + let arg = &this.thir[arg]; + // We don't do this in `as_rvalue` because we use `as_place` + // for borrow expressions, so we cannot create an `RValue` that + // remains valid across user code. `as_rvalue` is usually called + // by this method anyway, so this shouldn't cause too many + // unnecessary temporaries. + let arg_place = match borrow_kind { + BorrowKind::Shared => unpack!(block = this.as_read_only_place(block, arg)), + _ => unpack!(block = this.as_place(block, arg)), + }; + let borrow = Rvalue::Ref(this.tcx.lifetimes.re_erased, borrow_kind, arg_place); + this.cfg.push_assign(block, source_info, destination, borrow); + block.unit() + } + ExprKind::AddressOf { mutability, arg } => { + let arg = &this.thir[arg]; + let place = match mutability { + hir::Mutability::Not => this.as_read_only_place(block, arg), + hir::Mutability::Mut => this.as_place(block, arg), + }; + let address_of = Rvalue::AddressOf(mutability, unpack!(block = place)); + this.cfg.push_assign(block, source_info, destination, address_of); + block.unit() + } + ExprKind::Adt(box Adt { + adt_def, + variant_index, + substs, + user_ty, + ref fields, + ref base, + }) => { + // See the notes for `ExprKind::Array` in `as_rvalue` and for + // `ExprKind::Borrow` above. + let is_union = adt_def.is_union(); + let active_field_index = if is_union { Some(fields[0].name.index()) } else { None }; + + let scope = this.local_scope(); + + // first process the set of fields that were provided + // (evaluating them in order given by user) + let fields_map: FxHashMap<_, _> = fields + .into_iter() + .map(|f| { + let local_info = Box::new(LocalInfo::AggregateTemp); + ( + f.name, + unpack!( + block = this.as_operand( + block, + Some(scope), + &this.thir[f.expr], + Some(local_info), + NeedsTemporary::Maybe, + ) + ), + ) + }) + .collect(); + + let field_names: Vec<_> = + (0..adt_def.variant(variant_index).fields.len()).map(Field::new).collect(); + + let fields: Vec<_> = if let Some(FruInfo { base, field_types }) = base { + let place_builder = + unpack!(block = this.as_place_builder(block, &this.thir[*base])); + + // MIR does not natively support FRU, so for each + // base-supplied field, generate an operand that + // reads it from the base. + iter::zip(field_names, &**field_types) + .map(|(n, ty)| match fields_map.get(&n) { + Some(v) => v.clone(), + None => { + let place_builder = place_builder.clone(); + this.consume_by_copy_or_move( + place_builder + .field(n, *ty) + .into_place(this.tcx, this.typeck_results), + ) + } + }) + .collect() + } else { + field_names.iter().filter_map(|n| fields_map.get(n).cloned()).collect() + }; + + let inferred_ty = expr.ty; + let user_ty = user_ty.map(|ty| { + this.canonical_user_type_annotations.push(CanonicalUserTypeAnnotation { + span: source_info.span, + user_ty: ty, + inferred_ty, + }) + }); + let adt = Box::new(AggregateKind::Adt( + adt_def.did(), + variant_index, + substs, + user_ty, + active_field_index, + )); + this.cfg.push_assign( + block, + source_info, + destination, + Rvalue::Aggregate(adt, fields), + ); + block.unit() + } + ExprKind::InlineAsm { template, ref operands, options, line_spans } => { + use rustc_middle::{mir, thir}; + let operands = operands + .into_iter() + .map(|op| match *op { + thir::InlineAsmOperand::In { reg, expr } => mir::InlineAsmOperand::In { + reg, + value: unpack!(block = this.as_local_operand(block, &this.thir[expr])), + }, + thir::InlineAsmOperand::Out { reg, late, expr } => { + mir::InlineAsmOperand::Out { + reg, + late, + place: expr.map(|expr| { + unpack!(block = this.as_place(block, &this.thir[expr])) + }), + } + } + thir::InlineAsmOperand::InOut { reg, late, expr } => { + let place = unpack!(block = this.as_place(block, &this.thir[expr])); + mir::InlineAsmOperand::InOut { + reg, + late, + // This works because asm operands must be Copy + in_value: Operand::Copy(place), + out_place: Some(place), + } + } + thir::InlineAsmOperand::SplitInOut { reg, late, in_expr, out_expr } => { + mir::InlineAsmOperand::InOut { + reg, + late, + in_value: unpack!( + block = this.as_local_operand(block, &this.thir[in_expr]) + ), + out_place: out_expr.map(|out_expr| { + unpack!(block = this.as_place(block, &this.thir[out_expr])) + }), + } + } + thir::InlineAsmOperand::Const { value, span } => { + mir::InlineAsmOperand::Const { + value: Box::new(Constant { span, user_ty: None, literal: value }), + } + } + thir::InlineAsmOperand::SymFn { value, span } => { + mir::InlineAsmOperand::SymFn { + value: Box::new(Constant { span, user_ty: None, literal: value }), + } + } + thir::InlineAsmOperand::SymStatic { def_id } => { + mir::InlineAsmOperand::SymStatic { def_id } + } + }) + .collect(); + + if !options.contains(InlineAsmOptions::NORETURN) { + this.cfg.push_assign_unit(block, source_info, destination, this.tcx); + } + + let destination_block = this.cfg.start_new_block(); + this.cfg.terminate( + block, + source_info, + TerminatorKind::InlineAsm { + template, + operands, + options, + line_spans, + destination: if options.contains(InlineAsmOptions::NORETURN) { + None + } else { + Some(destination_block) + }, + cleanup: None, + }, + ); + if options.contains(InlineAsmOptions::MAY_UNWIND) { + this.diverge_from(block); + } + destination_block.unit() + } + + // These cases don't actually need a destination + ExprKind::Assign { .. } | ExprKind::AssignOp { .. } => { + unpack!(block = this.stmt_expr(block, expr, None)); + this.cfg.push_assign_unit(block, source_info, destination, this.tcx); + block.unit() + } + + ExprKind::Continue { .. } | ExprKind::Break { .. } | ExprKind::Return { .. } => { + unpack!(block = this.stmt_expr(block, expr, None)); + // No assign, as these have type `!`. + block.unit() + } + + // Avoid creating a temporary + ExprKind::VarRef { .. } + | ExprKind::UpvarRef { .. } + | ExprKind::PlaceTypeAscription { .. } + | ExprKind::ValueTypeAscription { .. } => { + debug_assert!(Category::of(&expr.kind) == Some(Category::Place)); + + let place = unpack!(block = this.as_place(block, expr)); + let rvalue = Rvalue::Use(this.consume_by_copy_or_move(place)); + this.cfg.push_assign(block, source_info, destination, rvalue); + block.unit() + } + ExprKind::Index { .. } | ExprKind::Deref { .. } | ExprKind::Field { .. } => { + debug_assert_eq!(Category::of(&expr.kind), Some(Category::Place)); + + // Create a "fake" temporary variable so that we check that the + // value is Sized. Usually, this is caught in type checking, but + // in the case of box expr there is no such check. + if !destination.projection.is_empty() { + this.local_decls.push(LocalDecl::new(expr.ty, expr.span)); + } + + let place = unpack!(block = this.as_place(block, expr)); + let rvalue = Rvalue::Use(this.consume_by_copy_or_move(place)); + this.cfg.push_assign(block, source_info, destination, rvalue); + block.unit() + } + + ExprKind::Yield { value } => { + let scope = this.local_scope(); + let value = unpack!( + block = this.as_operand( + block, + Some(scope), + &this.thir[value], + None, + NeedsTemporary::No + ) + ); + let resume = this.cfg.start_new_block(); + this.cfg.terminate( + block, + source_info, + TerminatorKind::Yield { value, resume, resume_arg: destination, drop: None }, + ); + this.generator_drop_cleanup(block); + resume.unit() + } + + // these are the cases that are more naturally handled by some other mode + ExprKind::Unary { .. } + | ExprKind::Binary { .. } + | ExprKind::Box { .. } + | ExprKind::Cast { .. } + | ExprKind::Pointer { .. } + | ExprKind::Repeat { .. } + | ExprKind::Array { .. } + | ExprKind::Tuple { .. } + | ExprKind::Closure { .. } + | ExprKind::ConstBlock { .. } + | ExprKind::Literal { .. } + | ExprKind::NamedConst { .. } + | ExprKind::NonHirLiteral { .. } + | ExprKind::ZstLiteral { .. } + | ExprKind::ConstParam { .. } + | ExprKind::ThreadLocalRef(_) + | ExprKind::StaticRef { .. } => { + debug_assert!(match Category::of(&expr.kind).unwrap() { + // should be handled above + Category::Rvalue(RvalueFunc::Into) => false, + + // must be handled above or else we get an + // infinite loop in the builder; see + // e.g., `ExprKind::VarRef` above + Category::Place => false, + + _ => true, + }); + + let rvalue = unpack!(block = this.as_local_rvalue(block, expr)); + this.cfg.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 + } + + fn is_let(&self, expr: ExprId) -> bool { + match self.thir[expr].kind { + ExprKind::Let { .. } => true, + ExprKind::Scope { value, .. } => self.is_let(value), + _ => false, + } + } +} |