]>
Commit | Line | Data |
---|---|---|
064997fb FG |
1 | //! This file implements "place projections"; basically a symmetric API for 3 types: MPlaceTy, OpTy, PlaceTy. |
2 | //! | |
f2b60f7d | 3 | //! OpTy and PlaceTy generally work by "let's see if we are actually an MPlaceTy, and do something custom if not". |
064997fb FG |
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 | ||
064997fb FG |
10 | use rustc_middle::mir; |
11 | use rustc_middle::ty; | |
add651ee | 12 | use rustc_middle::ty::layout::{LayoutOf, TyAndLayout}; |
fe692bf9 | 13 | use rustc_middle::ty::Ty; |
add651ee FG |
14 | use rustc_middle::ty::TyCtxt; |
15 | use rustc_target::abi::HasDataLayout; | |
16 | use rustc_target::abi::Size; | |
17 | use rustc_target::abi::{self, VariantIdx}; | |
18 | ||
19 | use super::{InterpCx, InterpResult, MPlaceTy, Machine, MemPlaceMeta, OpTy, Provenance, Scalar}; | |
064997fb | 20 | |
add651ee FG |
21 | /// A thing that we can project into, and that has a layout. |
22 | pub trait Projectable<'tcx, Prov: Provenance>: Sized + std::fmt::Debug { | |
23 | /// Get the layout. | |
24 | fn layout(&self) -> TyAndLayout<'tcx>; | |
25 | ||
26 | /// Get the metadata of a wide value. | |
27 | fn meta<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>( | |
28 | &self, | |
29 | ecx: &InterpCx<'mir, 'tcx, M>, | |
30 | ) -> InterpResult<'tcx, MemPlaceMeta<M::Provenance>>; | |
31 | ||
32 | fn len<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>( | |
33 | &self, | |
34 | ecx: &InterpCx<'mir, 'tcx, M>, | |
35 | ) -> InterpResult<'tcx, u64> { | |
36 | self.meta(ecx)?.len(self.layout(), ecx) | |
37 | } | |
38 | ||
39 | /// Offset the value by the given amount, replacing the layout and metadata. | |
40 | fn offset_with_meta( | |
41 | &self, | |
42 | offset: Size, | |
43 | meta: MemPlaceMeta<Prov>, | |
44 | layout: TyAndLayout<'tcx>, | |
45 | cx: &impl HasDataLayout, | |
46 | ) -> InterpResult<'tcx, Self>; | |
47 | ||
48 | fn offset( | |
49 | &self, | |
50 | offset: Size, | |
51 | layout: TyAndLayout<'tcx>, | |
52 | cx: &impl HasDataLayout, | |
53 | ) -> InterpResult<'tcx, Self> { | |
54 | assert!(layout.is_sized()); | |
55 | self.offset_with_meta(offset, MemPlaceMeta::None, layout, cx) | |
56 | } | |
57 | ||
58 | fn transmute( | |
59 | &self, | |
60 | layout: TyAndLayout<'tcx>, | |
61 | cx: &impl HasDataLayout, | |
62 | ) -> InterpResult<'tcx, Self> { | |
63 | assert_eq!(self.layout().size, layout.size); | |
64 | self.offset_with_meta(Size::ZERO, MemPlaceMeta::None, layout, cx) | |
65 | } | |
66 | ||
67 | /// Convert this to an `OpTy`. This might be an irreversible transformation, but is useful for | |
68 | /// reading from this thing. | |
69 | fn to_op<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>( | |
70 | &self, | |
71 | ecx: &InterpCx<'mir, 'tcx, M>, | |
72 | ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>>; | |
73 | } | |
064997fb FG |
74 | |
75 | // FIXME: Working around https://github.com/rust-lang/rust/issues/54385 | |
76 | impl<'mir, 'tcx: 'mir, Prov, M> InterpCx<'mir, 'tcx, M> | |
77 | where | |
f2b60f7d | 78 | Prov: Provenance + 'static, |
064997fb FG |
79 | M: Machine<'mir, 'tcx, Provenance = Prov>, |
80 | { | |
064997fb FG |
81 | /// Offset a pointer to project to a field of a struct/union. Unlike `place_field`, this is |
82 | /// always possible without allocating, so it can take `&self`. Also return the field's layout. | |
add651ee | 83 | /// This supports both struct and array fields, but not slices! |
064997fb FG |
84 | /// |
85 | /// This also works for arrays, but then the `usize` index type is restricting. | |
86 | /// For indexing into arrays, use `mplace_index`. | |
add651ee | 87 | pub fn project_field<P: Projectable<'tcx, M::Provenance>>( |
064997fb | 88 | &self, |
add651ee | 89 | base: &P, |
064997fb | 90 | field: usize, |
add651ee FG |
91 | ) -> InterpResult<'tcx, P> { |
92 | // Slices nominally have length 0, so they will panic somewhere in `fields.offset`. | |
93 | debug_assert!( | |
94 | !matches!(base.layout().ty.kind(), ty::Slice(..)), | |
95 | "`field` projection called on a slice -- call `index` projection instead" | |
96 | ); | |
97 | let offset = base.layout().fields.offset(field); | |
98 | let field_layout = base.layout().field(self, field); | |
064997fb FG |
99 | |
100 | // Offset may need adjustment for unsized fields. | |
101 | let (meta, offset) = if field_layout.is_unsized() { | |
add651ee FG |
102 | if base.layout().is_sized() { |
103 | // An unsized field of a sized type? Sure... | |
104 | // But const-prop actually feeds us such nonsense MIR! (see test `const_prop/issue-86351.rs`) | |
105 | throw_inval!(ConstPropNonsense); | |
106 | } | |
107 | let base_meta = base.meta(self)?; | |
064997fb FG |
108 | // Re-use parent metadata to determine dynamic field layout. |
109 | // With custom DSTS, this *will* execute user-defined code, but the same | |
110 | // happens at run-time so that's okay. | |
add651ee FG |
111 | match self.size_and_align_of(&base_meta, &field_layout)? { |
112 | Some((_, align)) => (base_meta, offset.align_to(align)), | |
064997fb FG |
113 | None => { |
114 | // For unsized types with an extern type tail we perform no adjustments. | |
115 | // NOTE: keep this in sync with `PlaceRef::project_field` in the codegen backend. | |
add651ee FG |
116 | assert!(matches!(base_meta, MemPlaceMeta::None)); |
117 | (base_meta, offset) | |
064997fb FG |
118 | } |
119 | } | |
120 | } else { | |
add651ee | 121 | // base_meta could be present; we might be accessing a sized field of an unsized |
064997fb FG |
122 | // struct. |
123 | (MemPlaceMeta::None, offset) | |
124 | }; | |
125 | ||
064997fb FG |
126 | base.offset_with_meta(offset, meta, field_layout, self) |
127 | } | |
128 | ||
add651ee FG |
129 | /// Downcasting to an enum variant. |
130 | pub fn project_downcast<P: Projectable<'tcx, M::Provenance>>( | |
064997fb | 131 | &self, |
add651ee | 132 | base: &P, |
064997fb | 133 | variant: VariantIdx, |
add651ee FG |
134 | ) -> InterpResult<'tcx, P> { |
135 | assert!(!base.meta(self)?.has_meta()); | |
064997fb FG |
136 | // Downcasts only change the layout. |
137 | // (In particular, no check about whether this is even the active variant -- that's by design, | |
138 | // see https://github.com/rust-lang/rust/issues/93688#issuecomment-1032929496.) | |
add651ee FG |
139 | // So we just "offset" by 0. |
140 | let layout = base.layout().for_variant(self, variant); | |
141 | if layout.abi.is_uninhabited() { | |
142 | // `read_discriminant` should have excluded uninhabited variants... but ConstProp calls | |
143 | // us on dead code. | |
144 | throw_inval!(ConstPropNonsense) | |
145 | } | |
146 | // This cannot be `transmute` as variants *can* have a smaller size than the entire enum. | |
147 | base.offset(Size::ZERO, layout, self) | |
064997fb FG |
148 | } |
149 | ||
add651ee FG |
150 | /// Compute the offset and field layout for accessing the given index. |
151 | pub fn project_index<P: Projectable<'tcx, M::Provenance>>( | |
064997fb | 152 | &self, |
add651ee | 153 | base: &P, |
064997fb | 154 | index: u64, |
add651ee | 155 | ) -> InterpResult<'tcx, P> { |
064997fb | 156 | // Not using the layout method because we want to compute on u64 |
add651ee | 157 | let (offset, field_layout) = match base.layout().fields { |
064997fb FG |
158 | abi::FieldsShape::Array { stride, count: _ } => { |
159 | // `count` is nonsense for slices, use the dynamic length instead. | |
160 | let len = base.len(self)?; | |
161 | if index >= len { | |
162 | // This can only be reached in ConstProp and non-rustc-MIR. | |
163 | throw_ub!(BoundsCheckFailed { len, index }); | |
164 | } | |
165 | let offset = stride * index; // `Size` multiplication | |
166 | // All fields have the same layout. | |
add651ee FG |
167 | let field_layout = base.layout().field(self, 0); |
168 | (offset, field_layout) | |
064997fb FG |
169 | } |
170 | _ => span_bug!( | |
171 | self.cur_span(), | |
172 | "`mplace_index` called on non-array type {:?}", | |
add651ee | 173 | base.layout().ty |
064997fb | 174 | ), |
064997fb | 175 | }; |
064997fb | 176 | |
add651ee | 177 | base.offset(offset, field_layout, self) |
064997fb FG |
178 | } |
179 | ||
add651ee | 180 | fn project_constant_index<P: Projectable<'tcx, M::Provenance>>( |
064997fb | 181 | &self, |
add651ee | 182 | base: &P, |
064997fb FG |
183 | offset: u64, |
184 | min_length: u64, | |
185 | from_end: bool, | |
add651ee | 186 | ) -> InterpResult<'tcx, P> { |
064997fb FG |
187 | let n = base.len(self)?; |
188 | if n < min_length { | |
189 | // This can only be reached in ConstProp and non-rustc-MIR. | |
190 | throw_ub!(BoundsCheckFailed { len: min_length, index: n }); | |
191 | } | |
192 | ||
193 | let index = if from_end { | |
194 | assert!(0 < offset && offset <= min_length); | |
195 | n.checked_sub(offset).unwrap() | |
196 | } else { | |
197 | assert!(offset < min_length); | |
198 | offset | |
199 | }; | |
200 | ||
add651ee | 201 | self.project_index(base, index) |
064997fb FG |
202 | } |
203 | ||
add651ee FG |
204 | /// Iterates over all fields of an array. Much more efficient than doing the |
205 | /// same by repeatedly calling `operand_index`. | |
206 | pub fn project_array_fields<'a, P: Projectable<'tcx, M::Provenance>>( | |
207 | &self, | |
208 | base: &'a P, | |
209 | ) -> InterpResult<'tcx, impl Iterator<Item = InterpResult<'tcx, P>> + 'a> | |
210 | where | |
211 | 'tcx: 'a, | |
212 | { | |
213 | let abi::FieldsShape::Array { stride, .. } = base.layout().fields else { | |
214 | span_bug!(self.cur_span(), "operand_array_fields: expected an array layout"); | |
215 | }; | |
216 | let len = base.len(self)?; | |
217 | let field_layout = base.layout().field(self, 0); | |
218 | let tcx: TyCtxt<'tcx> = *self.tcx; | |
219 | // `Size` multiplication | |
220 | Ok((0..len).map(move |i| { | |
221 | base.offset_with_meta(stride * i, MemPlaceMeta::None, field_layout, &tcx) | |
222 | })) | |
064997fb FG |
223 | } |
224 | ||
add651ee FG |
225 | /// Subslicing |
226 | fn project_subslice<P: Projectable<'tcx, M::Provenance>>( | |
064997fb | 227 | &self, |
add651ee | 228 | base: &P, |
064997fb FG |
229 | from: u64, |
230 | to: u64, | |
231 | from_end: bool, | |
add651ee | 232 | ) -> InterpResult<'tcx, P> { |
064997fb FG |
233 | let len = base.len(self)?; // also asserts that we have a type where this makes sense |
234 | let actual_to = if from_end { | |
235 | if from.checked_add(to).map_or(true, |to| to > len) { | |
236 | // This can only be reached in ConstProp and non-rustc-MIR. | |
237 | throw_ub!(BoundsCheckFailed { len: len, index: from.saturating_add(to) }); | |
238 | } | |
239 | len.checked_sub(to).unwrap() | |
240 | } else { | |
241 | to | |
242 | }; | |
243 | ||
244 | // Not using layout method because that works with usize, and does not work with slices | |
245 | // (that have count 0 in their layout). | |
add651ee | 246 | let from_offset = match base.layout().fields { |
064997fb FG |
247 | abi::FieldsShape::Array { stride, .. } => stride * from, // `Size` multiplication is checked |
248 | _ => { | |
add651ee FG |
249 | span_bug!( |
250 | self.cur_span(), | |
251 | "unexpected layout of index access: {:#?}", | |
252 | base.layout() | |
253 | ) | |
064997fb FG |
254 | } |
255 | }; | |
256 | ||
257 | // Compute meta and new layout | |
258 | let inner_len = actual_to.checked_sub(from).unwrap(); | |
add651ee | 259 | let (meta, ty) = match base.layout().ty.kind() { |
064997fb FG |
260 | // It is not nice to match on the type, but that seems to be the only way to |
261 | // implement this. | |
fe692bf9 FG |
262 | ty::Array(inner, _) => { |
263 | (MemPlaceMeta::None, Ty::new_array(self.tcx.tcx, *inner, inner_len)) | |
264 | } | |
064997fb | 265 | ty::Slice(..) => { |
9ffffee4 | 266 | let len = Scalar::from_target_usize(inner_len, self); |
add651ee | 267 | (MemPlaceMeta::Meta(len), base.layout().ty) |
064997fb FG |
268 | } |
269 | _ => { | |
add651ee FG |
270 | span_bug!( |
271 | self.cur_span(), | |
272 | "cannot subslice non-array type: `{:?}`", | |
273 | base.layout().ty | |
274 | ) | |
064997fb FG |
275 | } |
276 | }; | |
277 | let layout = self.layout_of(ty)?; | |
064997fb | 278 | |
add651ee | 279 | base.offset_with_meta(from_offset, meta, layout, self) |
064997fb FG |
280 | } |
281 | ||
add651ee | 282 | /// Applying a general projection |
064997fb | 283 | #[instrument(skip(self), level = "trace")] |
add651ee FG |
284 | pub fn project<P>(&self, base: &P, proj_elem: mir::PlaceElem<'tcx>) -> InterpResult<'tcx, P> |
285 | where | |
286 | P: Projectable<'tcx, M::Provenance> + From<MPlaceTy<'tcx, M::Provenance>> + std::fmt::Debug, | |
287 | { | |
064997fb FG |
288 | use rustc_middle::mir::ProjectionElem::*; |
289 | Ok(match proj_elem { | |
add651ee FG |
290 | OpaqueCast(ty) => base.transmute(self.layout_of(ty)?, self)?, |
291 | Field(field, _) => self.project_field(base, field.index())?, | |
292 | Downcast(_, variant) => self.project_downcast(base, variant)?, | |
293 | Deref => self.deref_pointer(&base.to_op(self)?)?.into(), | |
064997fb FG |
294 | Index(local) => { |
295 | let layout = self.layout_of(self.tcx.types.usize)?; | |
296 | let n = self.local_to_op(self.frame(), local, Some(layout))?; | |
9ffffee4 | 297 | let n = self.read_target_usize(&n)?; |
add651ee | 298 | self.project_index(base, n)? |
064997fb FG |
299 | } |
300 | ConstantIndex { offset, min_length, from_end } => { | |
add651ee | 301 | self.project_constant_index(base, offset, min_length, from_end)? |
064997fb | 302 | } |
add651ee | 303 | Subslice { from, to, from_end } => self.project_subslice(base, from, to, from_end)?, |
064997fb FG |
304 | }) |
305 | } | |
306 | } |