diff options
Diffstat (limited to 'compiler/rustc_mir_build/src/builder/expr/into.rs')
| -rw-r--r-- | compiler/rustc_mir_build/src/builder/expr/into.rs | 650 |
1 files changed, 650 insertions, 0 deletions
diff --git a/compiler/rustc_mir_build/src/builder/expr/into.rs b/compiler/rustc_mir_build/src/builder/expr/into.rs new file mode 100644 index 00000000000..88f63d4e22c --- /dev/null +++ b/compiler/rustc_mir_build/src/builder/expr/into.rs @@ -0,0 +1,650 @@ +//! See docs in build/expr/mod.rs + +use rustc_ast::{AsmMacro, InlineAsmOptions}; +use rustc_data_structures::fx::FxHashMap; +use rustc_data_structures::stack::ensure_sufficient_stack; +use rustc_hir as hir; +use rustc_middle::mir::*; +use rustc_middle::span_bug; +use rustc_middle::thir::*; +use rustc_middle::ty::CanonicalUserTypeAnnotation; +use rustc_span::source_map::Spanned; +use tracing::{debug, instrument}; + +use crate::builder::expr::category::{Category, RvalueFunc}; +use crate::builder::matches::DeclareLetBindings; +use crate::builder::{BlockAnd, BlockAndExtension, BlockFrame, Builder, NeedsTemporary}; + +impl<'a, 'tcx> Builder<'a, 'tcx> { + /// Compile `expr`, storing the result into `destination`, which + /// is assumed to be uninitialized. + #[instrument(level = "debug", skip(self))] + pub(crate) fn expr_into_dest( + &mut self, + destination: Place<'tcx>, + mut block: BasicBlock, + expr_id: ExprId, + ) -> BlockAnd<()> { + // 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 = &this.thir[expr_id]; + 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, value) + }) + }) + } + ExprKind::Block { block: ast_block } => { + this.ast_block(destination, block, ast_block, source_info) + } + ExprKind::Match { scrutinee, ref arms, .. } => this.match_expr( + destination, + block, + scrutinee, + arms, + expr_span, + this.thir[scrutinee].span, + ), + ExprKind::If { cond, then, else_opt, if_then_scope } => { + let then_span = this.thir[then].span; + let then_source_info = this.source_info(then_span); + let condition_scope = this.local_scope(); + + let then_and_else_blocks = this.in_scope( + (if_then_scope, then_source_info), + LintLevel::Inherited, + |this| { + // FIXME: Does this need extra logic to handle let-chains? + let source_info = if this.is_let(cond) { + let variable_scope = + this.new_source_scope(then_span, LintLevel::Inherited); + this.source_scope = variable_scope; + SourceInfo { span: then_span, scope: variable_scope } + } else { + this.source_info(then_span) + }; + + // Lower the condition, and have it branch into `then` and `else` blocks. + let (then_block, else_block) = + this.in_if_then_scope(condition_scope, then_span, |this| { + let then_blk = this + .then_else_break( + block, + cond, + Some(condition_scope), // Temp scope + source_info, + DeclareLetBindings::Yes, // Declare `let` bindings normally + ) + .into_block(); + + // Lower the `then` arm into its block. + this.expr_into_dest(destination, then_blk, then) + }); + + // Pack `(then_block, else_block)` into `BlockAnd<BasicBlock>`. + then_block.and(else_block) + }, + ); + + // Unpack `BlockAnd<BasicBlock>` into `(then_blk, else_blk)`. + let (then_blk, mut else_blk); + else_blk = unpack!(then_blk = then_and_else_blocks); + + // If there is an `else` arm, lower it into `else_blk`. + if let Some(else_expr) = else_opt { + else_blk = this.expr_into_dest(destination, else_blk, else_expr).into_block(); + } else { + // There is no `else` arm, so we know both arms have type `()`. + // Generate the implicit `else {}` by assigning unit. + let correct_si = this.source_info(expr_span.shrink_to_hi()); + this.cfg.push_assign_unit(else_blk, correct_si, destination, this.tcx); + } + + // The `then` and `else` arms have been lowered into their respective + // blocks, so make both of them meet up in a new block. + 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 { .. } => { + // After desugaring, `let` expressions should only appear inside `if` + // expressions or `match` guards, possibly nested within a let-chain. + // In both cases they are specifically handled by the lowerings of + // those expressions, so this case is currently unreachable. + span_bug!(expr_span, "unexpected let expression outside of if or match-guard"); + } + ExprKind::NeverToAny { source } => { + let source_expr = &this.thir[source]; + let is_call = + matches!(source_expr.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, this.local_temp_lifetime(), 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 } => { + let condition_scope = this.local_scope(); + let source_info = this.source_info(expr.span); + + this.visit_coverage_branch_operation(op, expr.span); + + // We first evaluate the left-hand side of the predicate ... + let (then_block, else_block) = + this.in_if_then_scope(condition_scope, expr.span, |this| { + this.then_else_break( + block, + lhs, + Some(condition_scope), // Temp scope + source_info, + // This flag controls how inner `let` expressions are lowered, + // but either way there shouldn't be any of those in here. + DeclareLetBindings::LetNotPermitted, + ) + }); + let (short_circuit, continuation, constant) = match op { + LogicalOp::And => (else_block, then_block, false), + LogicalOp::Or => (then_block, else_block, true), + }; + // At this point, the control flow splits into a short-circuiting path + // and a continuation path. + // - If the operator is `&&`, passing `lhs` leads to continuation of evaluation on `rhs`; + // failing it leads to the short-circuting path which assigns `false` to the place. + // - If the operator is `||`, failing `lhs` leads to continuation of evaluation on `rhs`; + // passing it leads to the short-circuting path which assigns `true` to the place. + this.cfg.push_assign_constant( + short_circuit, + source_info, + destination, + ConstOperand { + span: expr.span, + user_ty: None, + const_: Const::from_bool(this.tcx, constant), + }, + ); + let mut rhs_block = + this.expr_into_dest(destination, continuation, rhs).into_block(); + // Instrument the lowered RHS's value for condition coverage. + // (Does nothing if condition coverage is not enabled.) + this.visit_coverage_standalone_condition(rhs, destination, &mut rhs_block); + + let target = this.cfg.start_new_block(); + this.cfg.goto(rhs_block, source_info, target); + this.cfg.goto(short_circuit, source_info, target); + target.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: UnwindAction::Continue, + }); + 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 = this.expr_into_dest(tmp, body_block, body).into_block(); + 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, fun)); + let args: Box<[_]> = args + .into_iter() + .copied() + .map(|arg| Spanned { + node: unpack!(block = this.as_local_call_operand(block, arg)), + span: this.thir.exprs[arg].span, + }) + .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, + unwind: UnwindAction::Continue, + 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: expr + .ty + .is_inhabited_from( + this.tcx, + this.parent_module, + this.infcx.typing_env(this.param_env), + ) + .then_some(success), + call_source: if from_hir_call { + CallSource::Normal + } else { + CallSource::OverloadedOperator + }, + fn_span, + }); + this.diverge_from(block); + success.unit() + } + ExprKind::Use { source } => this.expr_into_dest(destination, block, source), + ExprKind::Borrow { arg, borrow_kind } => { + // 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::RawBorrow { mutability, 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::RawPtr(mutability, unpack!(block = place)); + this.cfg.push_assign(block, source_info, destination, address_of); + block.unit() + } + ExprKind::Adt(box AdtExpr { + adt_def, + variant_index, + args, + ref 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 = is_union.then(|| fields[0].name); + + let scope = this.local_temp_lifetime(); + + // 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| { + ( + f.name, + unpack!( + block = this.as_operand( + block, + scope, + f.expr, + LocalInfo::AggregateTemp, + NeedsTemporary::Maybe, + ) + ), + ) + }) + .collect(); + + let variant = adt_def.variant(variant_index); + let field_names = variant.fields.indices(); + + let fields = match base { + AdtExprBase::None => { + field_names.filter_map(|n| fields_map.get(&n).cloned()).collect() + } + AdtExprBase::Base(FruInfo { base, field_types }) => { + let place_builder = unpack!(block = this.as_place_builder(block, *base)); + + // We desugar FRU as we lower to MIR, so for each + // base-supplied field, generate an operand that + // reads it from the base. + itertools::zip_eq(field_names, &**field_types) + .map(|(n, ty)| match fields_map.get(&n) { + Some(v) => v.clone(), + None => { + let place = + place_builder.clone_project(PlaceElem::Field(n, *ty)); + this.consume_by_copy_or_move(place.to_place(this)) + } + }) + .collect() + } + AdtExprBase::DefaultFields(field_types) => { + itertools::zip_eq(field_names, field_types) + .map(|(n, &ty)| match fields_map.get(&n) { + Some(v) => v.clone(), + None => match variant.fields[n].value { + Some(def) => { + let value = Const::Unevaluated( + UnevaluatedConst::new(def, args), + ty, + ); + Operand::Constant(Box::new(ConstOperand { + span: expr_span, + user_ty: None, + const_: value, + })) + } + None => { + let name = variant.fields[n].name; + span_bug!( + expr_span, + "missing mandatory field `{name}` of type `{ty}`", + ); + } + }, + }) + .collect() + } + }; + + let inferred_ty = expr.ty; + let user_ty = user_ty.as_ref().map(|user_ty| { + this.canonical_user_type_annotations.push(CanonicalUserTypeAnnotation { + span: source_info.span, + user_ty: user_ty.clone(), + inferred_ty, + }) + }); + let adt = Box::new(AggregateKind::Adt( + adt_def.did(), + variant_index, + args, + user_ty, + active_field_index, + )); + this.cfg.push_assign( + block, + source_info, + destination, + Rvalue::Aggregate(adt, fields), + ); + block.unit() + } + ExprKind::InlineAsm(box InlineAsmExpr { + asm_macro, + template, + ref operands, + options, + line_spans, + }) => { + use rustc_middle::{mir, thir}; + + let destination_block = this.cfg.start_new_block(); + let mut targets = + if asm_macro.diverges(options) { vec![] } else { vec![destination_block] }; + + 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, expr)), + }, + thir::InlineAsmOperand::Out { reg, late, expr } => { + mir::InlineAsmOperand::Out { + reg, + late, + place: expr.map(|expr| unpack!(block = this.as_place(block, expr))), + } + } + thir::InlineAsmOperand::InOut { reg, late, expr } => { + let place = unpack!(block = this.as_place(block, 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, in_expr)), + out_place: out_expr.map(|out_expr| { + unpack!(block = this.as_place(block, out_expr)) + }), + } + } + thir::InlineAsmOperand::Const { value, span } => { + mir::InlineAsmOperand::Const { + value: Box::new(ConstOperand { + span, + user_ty: None, + const_: value, + }), + } + } + thir::InlineAsmOperand::SymFn { value, span } => { + mir::InlineAsmOperand::SymFn { + value: Box::new(ConstOperand { + span, + user_ty: None, + const_: value, + }), + } + } + thir::InlineAsmOperand::SymStatic { def_id } => { + mir::InlineAsmOperand::SymStatic { def_id } + } + thir::InlineAsmOperand::Label { block } => { + let target = this.cfg.start_new_block(); + let target_index = targets.len(); + targets.push(target); + + let tmp = this.get_unit_temp(); + let target = + this.ast_block(tmp, target, block, source_info).into_block(); + this.cfg.terminate(target, source_info, TerminatorKind::Goto { + target: destination_block, + }); + + mir::InlineAsmOperand::Label { target_index } + } + }) + .collect(); + + if !expr.ty.is_never() { + this.cfg.push_assign_unit(block, source_info, destination, this.tcx); + } + + let asm_macro = match asm_macro { + AsmMacro::Asm => InlineAsmMacro::Asm, + AsmMacro::GlobalAsm => { + span_bug!(expr_span, "unexpected global_asm! in inline asm") + } + AsmMacro::NakedAsm => InlineAsmMacro::NakedAsm, + }; + + this.cfg.terminate(block, source_info, TerminatorKind::InlineAsm { + asm_macro, + template, + operands, + options, + line_spans, + targets: targets.into_boxed_slice(), + unwind: if options.contains(InlineAsmOptions::MAY_UNWIND) { + UnwindAction::Continue + } else { + UnwindAction::Unreachable + }, + }); + 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 { .. } => { + block = this.stmt_expr(block, expr_id, None).into_block(); + this.cfg.push_assign_unit(block, source_info, destination, this.tcx); + block.unit() + } + + ExprKind::Continue { .. } + | ExprKind::Break { .. } + | ExprKind::Return { .. } + | ExprKind::Become { .. } => { + block = this.stmt_expr(block, expr_id, None).into_block(); + // 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_id)); + 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_id)); + 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_temp_lifetime(); + let value = unpack!( + block = + this.as_operand(block, scope, value, LocalInfo::Boring, 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.coroutine_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::PointerCoercion { .. } + | ExprKind::Repeat { .. } + | ExprKind::Array { .. } + | ExprKind::Tuple { .. } + | ExprKind::Closure { .. } + | ExprKind::ConstBlock { .. } + | ExprKind::Literal { .. } + | ExprKind::NamedConst { .. } + | ExprKind::NonHirLiteral { .. } + | ExprKind::ZstLiteral { .. } + | ExprKind::ConstParam { .. } + | ExprKind::ThreadLocalRef(_) + | ExprKind::StaticRef { .. } + | ExprKind::OffsetOf { .. } => { + 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_id)); + 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, + } + } +} |
