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