rustc_mir_build/builder/expr/as_operand.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
//! See docs in build/expr/mod.rs
use rustc_middle::mir::*;
use rustc_middle::thir::*;
use tracing::{debug, instrument};
use crate::builder::expr::category::Category;
use crate::builder::{BlockAnd, BlockAndExtension, Builder, NeedsTemporary};
impl<'a, 'tcx> Builder<'a, 'tcx> {
/// Construct a temporary lifetime restricted to just the local scope
pub(crate) fn local_temp_lifetime(&self) -> TempLifetime {
let local_scope = self.local_scope();
TempLifetime { temp_lifetime: Some(local_scope), backwards_incompatible: None }
}
/// Returns an operand suitable for use until the end of the current
/// scope expression.
///
/// The operand returned from this function will *not be valid*
/// after the current enclosing `ExprKind::Scope` has ended, so
/// please do *not* return it from functions to avoid bad
/// miscompiles.
pub(crate) fn as_local_operand(
&mut self,
block: BasicBlock,
expr_id: ExprId,
) -> BlockAnd<Operand<'tcx>> {
self.as_operand(
block,
self.local_temp_lifetime(),
expr_id,
LocalInfo::Boring,
NeedsTemporary::Maybe,
)
}
/// Returns an operand suitable for use until the end of the current scope expression and
/// suitable also to be passed as function arguments.
///
/// The operand returned from this function will *not be valid* after an ExprKind::Scope is
/// passed, so please do *not* return it from functions to avoid bad miscompiles. Returns an
/// operand suitable for use as a call argument. This is almost always equivalent to
/// `as_operand`, except for the particular case of passing values of (potentially) unsized
/// types "by value" (see details below).
///
/// The operand returned from this function will *not be valid*
/// after the current enclosing `ExprKind::Scope` has ended, so
/// please do *not* return it from functions to avoid bad
/// miscompiles.
///
/// # Parameters of unsized types
///
/// We tweak the handling of parameters of unsized type slightly to avoid the need to create a
/// local variable of unsized type. For example, consider this program:
///
/// ```
/// #![feature(unsized_locals, unsized_fn_params)]
/// # use core::fmt::Debug;
/// fn foo(p: dyn Debug) { dbg!(p); }
///
/// fn bar(box_p: Box<dyn Debug>) { foo(*box_p); }
/// ```
///
/// Ordinarily, for sized types, we would compile the call `foo(*p)` like so:
///
/// ```ignore (illustrative)
/// let tmp0 = *box_p; // tmp0 would be the operand returned by this function call
/// foo(tmp0)
/// ```
///
/// But because the parameter to `foo` is of the unsized type `dyn Debug`, and because it is
/// being moved the deref of a box, we compile it slightly differently. The temporary `tmp0`
/// that we create *stores the entire box*, and the parameter to the call itself will be
/// `*tmp0`:
///
/// ```ignore (illustrative)
/// let tmp0 = box_p; call foo(*tmp0)
/// ```
///
/// This way, the temporary `tmp0` that we create has type `Box<dyn Debug>`, which is sized.
/// The value passed to the call (`*tmp0`) still has the `dyn Debug` type -- but the way that
/// calls are compiled means that this parameter will be passed "by reference", meaning that we
/// will actually provide a pointer to the interior of the box, and not move the `dyn Debug`
/// value to the stack.
///
/// See #68304 for more details.
pub(crate) fn as_local_call_operand(
&mut self,
block: BasicBlock,
expr: ExprId,
) -> BlockAnd<Operand<'tcx>> {
self.as_call_operand(block, self.local_temp_lifetime(), expr)
}
/// Compile `expr` into a value that can be used as an operand.
/// If `expr` is a place like `x`, this will introduce a
/// temporary `tmp = x`, so that we capture the value of `x` at
/// this time.
///
/// If we end up needing to create a temporary, then we will use
/// `local_info` as its `LocalInfo`, unless `as_temporary`
/// has already assigned it a non-`None` `LocalInfo`.
/// Normally, you should use `None` for `local_info`
///
/// The operand is known to be live until the end of `scope`.
///
/// Like `as_local_call_operand`, except that the argument will
/// not be valid once `scope` ends.
#[instrument(level = "debug", skip(self, scope))]
pub(crate) fn as_operand(
&mut self,
mut block: BasicBlock,
scope: TempLifetime,
expr_id: ExprId,
local_info: LocalInfo<'tcx>,
needs_temporary: NeedsTemporary,
) -> BlockAnd<Operand<'tcx>> {
let this = self;
let expr = &this.thir[expr_id];
if let ExprKind::Scope { region_scope, lint_level, value } = expr.kind {
let source_info = this.source_info(expr.span);
let region_scope = (region_scope, source_info);
return this.in_scope(region_scope, lint_level, |this| {
this.as_operand(block, scope, value, local_info, needs_temporary)
});
}
let category = Category::of(&expr.kind).unwrap();
debug!(?category, ?expr.kind);
match category {
Category::Constant
if matches!(needs_temporary, NeedsTemporary::No)
|| !expr.ty.needs_drop(this.tcx, this.typing_env()) =>
{
let constant = this.as_constant(expr);
block.and(Operand::Constant(Box::new(constant)))
}
Category::Constant | Category::Place | Category::Rvalue(..) => {
let operand = unpack!(block = this.as_temp(block, scope, expr_id, Mutability::Mut));
// Overwrite temp local info if we have something more interesting to record.
if !matches!(local_info, LocalInfo::Boring) {
let decl_info =
this.local_decls[operand].local_info.as_mut().assert_crate_local();
if let LocalInfo::Boring | LocalInfo::BlockTailTemp(_) = **decl_info {
**decl_info = local_info;
}
}
block.and(Operand::Move(Place::from(operand)))
}
}
}
pub(crate) fn as_call_operand(
&mut self,
mut block: BasicBlock,
scope: TempLifetime,
expr_id: ExprId,
) -> BlockAnd<Operand<'tcx>> {
let this = self;
let expr = &this.thir[expr_id];
debug!("as_call_operand(block={:?}, expr={:?})", block, expr);
if let ExprKind::Scope { region_scope, lint_level, value } = expr.kind {
let source_info = this.source_info(expr.span);
let region_scope = (region_scope, source_info);
return this.in_scope(region_scope, lint_level, |this| {
this.as_call_operand(block, scope, value)
});
}
let tcx = this.tcx;
if tcx.features().unsized_fn_params() {
let ty = expr.ty;
if !ty.is_sized(tcx, this.typing_env()) {
// !sized means !copy, so this is an unsized move
assert!(!tcx.type_is_copy_modulo_regions(this.typing_env(), ty));
// As described above, detect the case where we are passing a value of unsized
// type, and that value is coming from the deref of a box.
if let ExprKind::Deref { arg } = expr.kind {
// Generate let tmp0 = arg0
let operand = unpack!(block = this.as_temp(block, scope, arg, Mutability::Mut));
// Return the operand *tmp0 to be used as the call argument
let place = Place {
local: operand,
projection: tcx.mk_place_elems(&[PlaceElem::Deref]),
};
return block.and(Operand::Move(place));
}
}
}
this.as_operand(block, scope, expr_id, LocalInfo::Boring, NeedsTemporary::Maybe)
}
}