]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_const_eval/src/interpret/projection.rs
Rollup merge of #106113 - krasimirgg:llvm-16-ext-tyid, r=nikic
[rust.git] / compiler / rustc_const_eval / src / interpret / projection.rs
1 //! This file implements "place projections"; basically a symmetric API for 3 types: MPlaceTy, OpTy, PlaceTy.
2 //!
3 //! OpTy and PlaceTy generally work by "let's see if we are actually an MPlaceTy, and do something custom if not".
4 //! For PlaceTy, the custom thing is basically always to call `force_allocation` and then use the MPlaceTy logic anyway.
5 //! For OpTy, the custom thing on field pojections has to be pretty clever (since `Operand::Immediate` can have fields),
6 //! but for array/slice operations it only has to worry about `Operand::Uninit`. That makes the value part trivial,
7 //! but we still need to do bounds checking and adjust the layout. To not duplicate that with MPlaceTy, we actually
8 //! implement the logic on OpTy, and MPlaceTy calls that.
9
10 use either::{Left, Right};
11
12 use rustc_middle::mir;
13 use rustc_middle::ty;
14 use rustc_middle::ty::layout::LayoutOf;
15 use rustc_target::abi::{self, Abi, VariantIdx};
16
17 use super::{
18     ImmTy, Immediate, InterpCx, InterpResult, MPlaceTy, Machine, MemPlaceMeta, OpTy, PlaceTy,
19     Provenance, Scalar,
20 };
21
22 // FIXME: Working around https://github.com/rust-lang/rust/issues/54385
23 impl<'mir, 'tcx: 'mir, Prov, M> InterpCx<'mir, 'tcx, M>
24 where
25     Prov: Provenance + 'static,
26     M: Machine<'mir, 'tcx, Provenance = Prov>,
27 {
28     //# Field access
29
30     /// Offset a pointer to project to a field of a struct/union. Unlike `place_field`, this is
31     /// always possible without allocating, so it can take `&self`. Also return the field's layout.
32     /// This supports both struct and array fields.
33     ///
34     /// This also works for arrays, but then the `usize` index type is restricting.
35     /// For indexing into arrays, use `mplace_index`.
36     pub fn mplace_field(
37         &self,
38         base: &MPlaceTy<'tcx, M::Provenance>,
39         field: usize,
40     ) -> InterpResult<'tcx, MPlaceTy<'tcx, M::Provenance>> {
41         let offset = base.layout.fields.offset(field);
42         let field_layout = base.layout.field(self, field);
43
44         // Offset may need adjustment for unsized fields.
45         let (meta, offset) = if field_layout.is_unsized() {
46             // Re-use parent metadata to determine dynamic field layout.
47             // With custom DSTS, this *will* execute user-defined code, but the same
48             // happens at run-time so that's okay.
49             match self.size_and_align_of(&base.meta, &field_layout)? {
50                 Some((_, align)) => (base.meta, offset.align_to(align)),
51                 None => {
52                     // For unsized types with an extern type tail we perform no adjustments.
53                     // NOTE: keep this in sync with `PlaceRef::project_field` in the codegen backend.
54                     assert!(matches!(base.meta, MemPlaceMeta::None));
55                     (base.meta, offset)
56                 }
57             }
58         } else {
59             // base.meta could be present; we might be accessing a sized field of an unsized
60             // struct.
61             (MemPlaceMeta::None, offset)
62         };
63
64         // We do not look at `base.layout.align` nor `field_layout.align`, unlike
65         // codegen -- mostly to see if we can get away with that
66         base.offset_with_meta(offset, meta, field_layout, self)
67     }
68
69     /// Gets the place of a field inside the place, and also the field's type.
70     /// Just a convenience function, but used quite a bit.
71     /// This is the only projection that might have a side-effect: We cannot project
72     /// into the field of a local `ScalarPair`, we have to first allocate it.
73     pub fn place_field(
74         &mut self,
75         base: &PlaceTy<'tcx, M::Provenance>,
76         field: usize,
77     ) -> InterpResult<'tcx, PlaceTy<'tcx, M::Provenance>> {
78         // FIXME: We could try to be smarter and avoid allocation for fields that span the
79         // entire place.
80         let base = self.force_allocation(base)?;
81         Ok(self.mplace_field(&base, field)?.into())
82     }
83
84     pub fn operand_field(
85         &self,
86         base: &OpTy<'tcx, M::Provenance>,
87         field: usize,
88     ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> {
89         let base = match base.as_mplace_or_imm() {
90             Left(ref mplace) => {
91                 // We can reuse the mplace field computation logic for indirect operands.
92                 let field = self.mplace_field(mplace, field)?;
93                 return Ok(field.into());
94             }
95             Right(value) => value,
96         };
97
98         let field_layout = base.layout.field(self, field);
99         let offset = base.layout.fields.offset(field);
100         // This makes several assumptions about what layouts we will encounter; we match what
101         // codegen does as good as we can (see `extract_field` in `rustc_codegen_ssa/src/mir/operand.rs`).
102         let field_val: Immediate<_> = match (*base, base.layout.abi) {
103             // if the entire value is uninit, then so is the field (can happen in ConstProp)
104             (Immediate::Uninit, _) => Immediate::Uninit,
105             // the field contains no information, can be left uninit
106             _ if field_layout.is_zst() => Immediate::Uninit,
107             // the field covers the entire type
108             _ if field_layout.size == base.layout.size => {
109                 assert!(match (base.layout.abi, field_layout.abi) {
110                     (Abi::Scalar(..), Abi::Scalar(..)) => true,
111                     (Abi::ScalarPair(..), Abi::ScalarPair(..)) => true,
112                     _ => false,
113                 });
114                 assert!(offset.bytes() == 0);
115                 *base
116             }
117             // extract fields from types with `ScalarPair` ABI
118             (Immediate::ScalarPair(a_val, b_val), Abi::ScalarPair(a, b)) => {
119                 assert!(matches!(field_layout.abi, Abi::Scalar(..)));
120                 Immediate::from(if offset.bytes() == 0 {
121                     debug_assert_eq!(field_layout.size, a.size(self));
122                     a_val
123                 } else {
124                     debug_assert_eq!(offset, a.size(self).align_to(b.align(self).abi));
125                     debug_assert_eq!(field_layout.size, b.size(self));
126                     b_val
127                 })
128             }
129             // everything else is a bug
130             _ => span_bug!(
131                 self.cur_span(),
132                 "invalid field access on immediate {}, layout {:#?}",
133                 base,
134                 base.layout
135             ),
136         };
137
138         Ok(ImmTy::from_immediate(field_val, field_layout).into())
139     }
140
141     //# Downcasting
142
143     pub fn mplace_downcast(
144         &self,
145         base: &MPlaceTy<'tcx, M::Provenance>,
146         variant: VariantIdx,
147     ) -> InterpResult<'tcx, MPlaceTy<'tcx, M::Provenance>> {
148         // Downcasts only change the layout.
149         // (In particular, no check about whether this is even the active variant -- that's by design,
150         // see https://github.com/rust-lang/rust/issues/93688#issuecomment-1032929496.)
151         assert!(!base.meta.has_meta());
152         let mut base = *base;
153         base.layout = base.layout.for_variant(self, variant);
154         Ok(base)
155     }
156
157     pub fn place_downcast(
158         &self,
159         base: &PlaceTy<'tcx, M::Provenance>,
160         variant: VariantIdx,
161     ) -> InterpResult<'tcx, PlaceTy<'tcx, M::Provenance>> {
162         // Downcast just changes the layout
163         let mut base = base.clone();
164         base.layout = base.layout.for_variant(self, variant);
165         Ok(base)
166     }
167
168     pub fn operand_downcast(
169         &self,
170         base: &OpTy<'tcx, M::Provenance>,
171         variant: VariantIdx,
172     ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> {
173         // Downcast just changes the layout
174         let mut base = base.clone();
175         base.layout = base.layout.for_variant(self, variant);
176         Ok(base)
177     }
178
179     //# Slice indexing
180
181     #[inline(always)]
182     pub fn operand_index(
183         &self,
184         base: &OpTy<'tcx, M::Provenance>,
185         index: u64,
186     ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> {
187         // Not using the layout method because we want to compute on u64
188         match base.layout.fields {
189             abi::FieldsShape::Array { stride, count: _ } => {
190                 // `count` is nonsense for slices, use the dynamic length instead.
191                 let len = base.len(self)?;
192                 if index >= len {
193                     // This can only be reached in ConstProp and non-rustc-MIR.
194                     throw_ub!(BoundsCheckFailed { len, index });
195                 }
196                 let offset = stride * index; // `Size` multiplication
197                 // All fields have the same layout.
198                 let field_layout = base.layout.field(self, 0);
199                 base.offset(offset, field_layout, self)
200             }
201             _ => span_bug!(
202                 self.cur_span(),
203                 "`mplace_index` called on non-array type {:?}",
204                 base.layout.ty
205             ),
206         }
207     }
208
209     /// Iterates over all fields of an array. Much more efficient than doing the
210     /// same by repeatedly calling `operand_index`.
211     pub fn operand_array_fields<'a>(
212         &self,
213         base: &'a OpTy<'tcx, Prov>,
214     ) -> InterpResult<'tcx, impl Iterator<Item = InterpResult<'tcx, OpTy<'tcx, Prov>>> + 'a> {
215         let len = base.len(self)?; // also asserts that we have a type where this makes sense
216         let abi::FieldsShape::Array { stride, .. } = base.layout.fields else {
217             span_bug!(self.cur_span(), "operand_array_fields: expected an array layout");
218         };
219         let field_layout = base.layout.field(self, 0);
220         let dl = &self.tcx.data_layout;
221         // `Size` multiplication
222         Ok((0..len).map(move |i| base.offset(stride * i, field_layout, dl)))
223     }
224
225     /// Index into an array.
226     pub fn mplace_index(
227         &self,
228         base: &MPlaceTy<'tcx, M::Provenance>,
229         index: u64,
230     ) -> InterpResult<'tcx, MPlaceTy<'tcx, M::Provenance>> {
231         Ok(self.operand_index(&base.into(), index)?.assert_mem_place())
232     }
233
234     pub fn place_index(
235         &mut self,
236         base: &PlaceTy<'tcx, M::Provenance>,
237         index: u64,
238     ) -> InterpResult<'tcx, PlaceTy<'tcx, M::Provenance>> {
239         // There's not a lot we can do here, since we cannot have a place to a part of a local. If
240         // we are accessing the only element of a 1-element array, it's still the entire local...
241         // that doesn't seem worth it.
242         let base = self.force_allocation(base)?;
243         Ok(self.mplace_index(&base, index)?.into())
244     }
245
246     //# ConstantIndex support
247
248     fn operand_constant_index(
249         &self,
250         base: &OpTy<'tcx, M::Provenance>,
251         offset: u64,
252         min_length: u64,
253         from_end: bool,
254     ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> {
255         let n = base.len(self)?;
256         if n < min_length {
257             // This can only be reached in ConstProp and non-rustc-MIR.
258             throw_ub!(BoundsCheckFailed { len: min_length, index: n });
259         }
260
261         let index = if from_end {
262             assert!(0 < offset && offset <= min_length);
263             n.checked_sub(offset).unwrap()
264         } else {
265             assert!(offset < min_length);
266             offset
267         };
268
269         self.operand_index(base, index)
270     }
271
272     fn place_constant_index(
273         &mut self,
274         base: &PlaceTy<'tcx, M::Provenance>,
275         offset: u64,
276         min_length: u64,
277         from_end: bool,
278     ) -> InterpResult<'tcx, PlaceTy<'tcx, M::Provenance>> {
279         let base = self.force_allocation(base)?;
280         Ok(self
281             .operand_constant_index(&base.into(), offset, min_length, from_end)?
282             .assert_mem_place()
283             .into())
284     }
285
286     //# Subslicing
287
288     fn operand_subslice(
289         &self,
290         base: &OpTy<'tcx, M::Provenance>,
291         from: u64,
292         to: u64,
293         from_end: bool,
294     ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> {
295         let len = base.len(self)?; // also asserts that we have a type where this makes sense
296         let actual_to = if from_end {
297             if from.checked_add(to).map_or(true, |to| to > len) {
298                 // This can only be reached in ConstProp and non-rustc-MIR.
299                 throw_ub!(BoundsCheckFailed { len: len, index: from.saturating_add(to) });
300             }
301             len.checked_sub(to).unwrap()
302         } else {
303             to
304         };
305
306         // Not using layout method because that works with usize, and does not work with slices
307         // (that have count 0 in their layout).
308         let from_offset = match base.layout.fields {
309             abi::FieldsShape::Array { stride, .. } => stride * from, // `Size` multiplication is checked
310             _ => {
311                 span_bug!(self.cur_span(), "unexpected layout of index access: {:#?}", base.layout)
312             }
313         };
314
315         // Compute meta and new layout
316         let inner_len = actual_to.checked_sub(from).unwrap();
317         let (meta, ty) = match base.layout.ty.kind() {
318             // It is not nice to match on the type, but that seems to be the only way to
319             // implement this.
320             ty::Array(inner, _) => (MemPlaceMeta::None, self.tcx.mk_array(*inner, inner_len)),
321             ty::Slice(..) => {
322                 let len = Scalar::from_machine_usize(inner_len, self);
323                 (MemPlaceMeta::Meta(len), base.layout.ty)
324             }
325             _ => {
326                 span_bug!(self.cur_span(), "cannot subslice non-array type: `{:?}`", base.layout.ty)
327             }
328         };
329         let layout = self.layout_of(ty)?;
330         base.offset_with_meta(from_offset, meta, layout, self)
331     }
332
333     pub fn place_subslice(
334         &mut self,
335         base: &PlaceTy<'tcx, M::Provenance>,
336         from: u64,
337         to: u64,
338         from_end: bool,
339     ) -> InterpResult<'tcx, PlaceTy<'tcx, M::Provenance>> {
340         let base = self.force_allocation(base)?;
341         Ok(self.operand_subslice(&base.into(), from, to, from_end)?.assert_mem_place().into())
342     }
343
344     //# Applying a general projection
345
346     /// Projects into a place.
347     #[instrument(skip(self), level = "trace")]
348     pub fn place_projection(
349         &mut self,
350         base: &PlaceTy<'tcx, M::Provenance>,
351         proj_elem: mir::PlaceElem<'tcx>,
352     ) -> InterpResult<'tcx, PlaceTy<'tcx, M::Provenance>> {
353         use rustc_middle::mir::ProjectionElem::*;
354         Ok(match proj_elem {
355             OpaqueCast(ty) => {
356                 let mut place = base.clone();
357                 place.layout = self.layout_of(ty)?;
358                 place
359             }
360             Field(field, _) => self.place_field(base, field.index())?,
361             Downcast(_, variant) => self.place_downcast(base, variant)?,
362             Deref => self.deref_operand(&self.place_to_op(base)?)?.into(),
363             Index(local) => {
364                 let layout = self.layout_of(self.tcx.types.usize)?;
365                 let n = self.local_to_op(self.frame(), local, Some(layout))?;
366                 let n = self.read_machine_usize(&n)?;
367                 self.place_index(base, n)?
368             }
369             ConstantIndex { offset, min_length, from_end } => {
370                 self.place_constant_index(base, offset, min_length, from_end)?
371             }
372             Subslice { from, to, from_end } => self.place_subslice(base, from, to, from_end)?,
373         })
374     }
375
376     #[instrument(skip(self), level = "trace")]
377     pub fn operand_projection(
378         &self,
379         base: &OpTy<'tcx, M::Provenance>,
380         proj_elem: mir::PlaceElem<'tcx>,
381     ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> {
382         use rustc_middle::mir::ProjectionElem::*;
383         Ok(match proj_elem {
384             OpaqueCast(ty) => {
385                 let mut op = base.clone();
386                 op.layout = self.layout_of(ty)?;
387                 op
388             }
389             Field(field, _) => self.operand_field(base, field.index())?,
390             Downcast(_, variant) => self.operand_downcast(base, variant)?,
391             Deref => self.deref_operand(base)?.into(),
392             Index(local) => {
393                 let layout = self.layout_of(self.tcx.types.usize)?;
394                 let n = self.local_to_op(self.frame(), local, Some(layout))?;
395                 let n = self.read_machine_usize(&n)?;
396                 self.operand_index(base, n)?
397             }
398             ConstantIndex { offset, min_length, from_end } => {
399                 self.operand_constant_index(base, offset, min_length, from_end)?
400             }
401             Subslice { from, to, from_end } => self.operand_subslice(base, from, to, from_end)?,
402         })
403     }
404 }