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