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