• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! This module specifies the type based interner for constants.
2 //!
3 //! After a const evaluation has computed a value, before we destroy the const evaluator's session
4 //! memory, we need to extract all memory allocations to the global memory pool so they stay around.
5 //!
6 //! In principle, this is not very complicated: we recursively walk the final value, follow all the
7 //! pointers, and move all reachable allocations to the global `tcx` memory. The only complication
8 //! is picking the right mutability for the allocations in a `static` initializer: we want to make
9 //! as many allocations as possible immutable so LLVM can put them into read-only memory. At the
10 //! same time, we need to make memory that could be mutated by the program mutable to avoid
11 //! incorrect compilations. To achieve this, we do a type-based traversal of the final value,
12 //! tracking mutable and shared references and `UnsafeCell` to determine the current mutability.
13 //! (In principle, we could skip this type-based part for `const` and promoteds, as they need to be
14 //! always immutable. At least for `const` however we use this opportunity to reject any `const`
15 //! that contains allocations whose mutability we cannot identify.)
16 
17 use super::validity::RefTracking;
18 use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
19 use rustc_errors::ErrorGuaranteed;
20 use rustc_hir as hir;
21 use rustc_middle::mir::interpret::InterpResult;
22 use rustc_middle::ty::{self, layout::TyAndLayout, Ty};
23 
24 use rustc_ast::Mutability;
25 
26 use super::{
27     AllocId, Allocation, ConstAllocation, InterpCx, MPlaceTy, Machine, MemoryKind, PlaceTy,
28     ValueVisitor,
29 };
30 use crate::const_eval;
31 use crate::errors::{DanglingPtrInFinal, UnsupportedUntypedPointer};
32 
33 pub trait CompileTimeMachine<'mir, 'tcx, T> = Machine<
34         'mir,
35         'tcx,
36         MemoryKind = T,
37         Provenance = AllocId,
38         ExtraFnVal = !,
39         FrameExtra = (),
40         AllocExtra = (),
41         MemoryMap = FxIndexMap<AllocId, (MemoryKind<T>, Allocation)>,
42     >;
43 
44 struct InternVisitor<'rt, 'mir, 'tcx, M: CompileTimeMachine<'mir, 'tcx, const_eval::MemoryKind>> {
45     /// The ectx from which we intern.
46     ecx: &'rt mut InterpCx<'mir, 'tcx, M>,
47     /// Previously encountered safe references.
48     ref_tracking: &'rt mut RefTracking<(MPlaceTy<'tcx>, InternMode)>,
49     /// A list of all encountered allocations. After type-based interning, we traverse this list to
50     /// also intern allocations that are only referenced by a raw pointer or inside a union.
51     leftover_allocations: &'rt mut FxIndexSet<AllocId>,
52     /// The root kind of the value that we're looking at. This field is never mutated for a
53     /// particular allocation. It is primarily used to make as many allocations as possible
54     /// read-only so LLVM can place them in const memory.
55     mode: InternMode,
56     /// This field stores whether we are *currently* inside an `UnsafeCell`. This can affect
57     /// the intern mode of references we encounter.
58     inside_unsafe_cell: bool,
59 }
60 
61 #[derive(Copy, Clone, Debug, PartialEq, Hash, Eq)]
62 enum InternMode {
63     /// A static and its current mutability. Below shared references inside a `static mut`,
64     /// this is *immutable*, and below mutable references inside an `UnsafeCell`, this
65     /// is *mutable*.
66     Static(hir::Mutability),
67     /// A `const`.
68     Const,
69 }
70 
71 /// Signalling data structure to ensure we don't recurse
72 /// into the memory of other constants or statics
73 struct IsStaticOrFn;
74 
75 /// Intern an allocation without looking at its children.
76 /// `mode` is the mode of the environment where we found this pointer.
77 /// `mutability` is the mutability of the place to be interned; even if that says
78 /// `immutable` things might become mutable if `ty` is not frozen.
79 /// `ty` can be `None` if there is no potential interior mutability
80 /// to account for (e.g. for vtables).
intern_shallow<'rt, 'mir, 'tcx, M: CompileTimeMachine<'mir, 'tcx, const_eval::MemoryKind>>( ecx: &'rt mut InterpCx<'mir, 'tcx, M>, leftover_allocations: &'rt mut FxIndexSet<AllocId>, alloc_id: AllocId, mode: InternMode, ty: Option<Ty<'tcx>>, ) -> Option<IsStaticOrFn>81 fn intern_shallow<'rt, 'mir, 'tcx, M: CompileTimeMachine<'mir, 'tcx, const_eval::MemoryKind>>(
82     ecx: &'rt mut InterpCx<'mir, 'tcx, M>,
83     leftover_allocations: &'rt mut FxIndexSet<AllocId>,
84     alloc_id: AllocId,
85     mode: InternMode,
86     ty: Option<Ty<'tcx>>,
87 ) -> Option<IsStaticOrFn> {
88     trace!("intern_shallow {:?} with {:?}", alloc_id, mode);
89     // remove allocation
90     let tcx = ecx.tcx;
91     let Some((kind, mut alloc)) = ecx.memory.alloc_map.remove(&alloc_id) else {
92         // Pointer not found in local memory map. It is either a pointer to the global
93         // map, or dangling.
94         // If the pointer is dangling (neither in local nor global memory), we leave it
95         // to validation to error -- it has the much better error messages, pointing out where
96         // in the value the dangling reference lies.
97         // The `delay_span_bug` ensures that we don't forget such a check in validation.
98         if tcx.try_get_global_alloc(alloc_id).is_none() {
99             tcx.sess.delay_span_bug(ecx.tcx.span, "tried to intern dangling pointer");
100         }
101         // treat dangling pointers like other statics
102         // just to stop trying to recurse into them
103         return Some(IsStaticOrFn);
104     };
105     // This match is just a canary for future changes to `MemoryKind`, which most likely need
106     // changes in this function.
107     match kind {
108         MemoryKind::Stack
109         | MemoryKind::Machine(const_eval::MemoryKind::Heap)
110         | MemoryKind::CallerLocation => {}
111     }
112     // Set allocation mutability as appropriate. This is used by LLVM to put things into
113     // read-only memory, and also by Miri when evaluating other globals that
114     // access this one.
115     if let InternMode::Static(mutability) = mode {
116         // For this, we need to take into account `UnsafeCell`. When `ty` is `None`, we assume
117         // no interior mutability.
118         let frozen = ty.map_or(true, |ty| ty.is_freeze(*ecx.tcx, ecx.param_env));
119         // For statics, allocation mutability is the combination of place mutability and
120         // type mutability.
121         // The entire allocation needs to be mutable if it contains an `UnsafeCell` anywhere.
122         let immutable = mutability == Mutability::Not && frozen;
123         if immutable {
124             alloc.mutability = Mutability::Not;
125         } else {
126             // Just making sure we are not "upgrading" an immutable allocation to mutable.
127             assert_eq!(alloc.mutability, Mutability::Mut);
128         }
129     } else {
130         // No matter what, *constants are never mutable*. Mutating them is UB.
131         // See const_eval::machine::MemoryExtra::can_access_statics for why
132         // immutability is so important.
133 
134         // Validation will ensure that there is no `UnsafeCell` on an immutable allocation.
135         alloc.mutability = Mutability::Not;
136     };
137     // link the alloc id to the actual allocation
138     leftover_allocations.extend(alloc.provenance().ptrs().iter().map(|&(_, alloc_id)| alloc_id));
139     let alloc = tcx.mk_const_alloc(alloc);
140     tcx.set_alloc_id_memory(alloc_id, alloc);
141     None
142 }
143 
144 impl<'rt, 'mir, 'tcx, M: CompileTimeMachine<'mir, 'tcx, const_eval::MemoryKind>>
145     InternVisitor<'rt, 'mir, 'tcx, M>
146 {
intern_shallow( &mut self, alloc_id: AllocId, mode: InternMode, ty: Option<Ty<'tcx>>, ) -> Option<IsStaticOrFn>147     fn intern_shallow(
148         &mut self,
149         alloc_id: AllocId,
150         mode: InternMode,
151         ty: Option<Ty<'tcx>>,
152     ) -> Option<IsStaticOrFn> {
153         intern_shallow(self.ecx, self.leftover_allocations, alloc_id, mode, ty)
154     }
155 }
156 
157 impl<'rt, 'mir, 'tcx: 'mir, M: CompileTimeMachine<'mir, 'tcx, const_eval::MemoryKind>>
158     ValueVisitor<'mir, 'tcx, M> for InternVisitor<'rt, 'mir, 'tcx, M>
159 {
160     type V = MPlaceTy<'tcx>;
161 
162     #[inline(always)]
ecx(&self) -> &InterpCx<'mir, 'tcx, M>163     fn ecx(&self) -> &InterpCx<'mir, 'tcx, M> {
164         &self.ecx
165     }
166 
visit_aggregate( &mut self, mplace: &MPlaceTy<'tcx>, fields: impl Iterator<Item = InterpResult<'tcx, Self::V>>, ) -> InterpResult<'tcx>167     fn visit_aggregate(
168         &mut self,
169         mplace: &MPlaceTy<'tcx>,
170         fields: impl Iterator<Item = InterpResult<'tcx, Self::V>>,
171     ) -> InterpResult<'tcx> {
172         // We want to walk the aggregate to look for references to intern. While doing that we
173         // also need to take special care of interior mutability.
174         //
175         // As an optimization, however, if the allocation does not contain any references: we don't
176         // need to do the walk. It can be costly for big arrays for example (e.g. issue #93215).
177         let is_walk_needed = |mplace: &MPlaceTy<'tcx>| -> InterpResult<'tcx, bool> {
178             // ZSTs cannot contain pointers, we can avoid the interning walk.
179             if mplace.layout.is_zst() {
180                 return Ok(false);
181             }
182 
183             // Now, check whether this allocation could contain references.
184             //
185             // Note, this check may sometimes not be cheap, so we only do it when the walk we'd like
186             // to avoid could be expensive: on the potentially larger types, arrays and slices,
187             // rather than on all aggregates unconditionally.
188             if matches!(mplace.layout.ty.kind(), ty::Array(..) | ty::Slice(..)) {
189                 let Some((size, align)) = self.ecx.size_and_align_of_mplace(&mplace)? else {
190                     // We do the walk if we can't determine the size of the mplace: we may be
191                     // dealing with extern types here in the future.
192                     return Ok(true);
193                 };
194 
195                 // If there is no provenance in this allocation, it does not contain references
196                 // that point to another allocation, and we can avoid the interning walk.
197                 if let Some(alloc) = self.ecx.get_ptr_alloc(mplace.ptr, size, align)? {
198                     if !alloc.has_provenance() {
199                         return Ok(false);
200                     }
201                 } else {
202                     // We're encountering a ZST here, and can avoid the walk as well.
203                     return Ok(false);
204                 }
205             }
206 
207             // In the general case, we do the walk.
208             Ok(true)
209         };
210 
211         // If this allocation contains no references to intern, we avoid the potentially costly
212         // walk.
213         //
214         // We can do this before the checks for interior mutability below, because only references
215         // are relevant in that situation, and we're checking if there are any here.
216         if !is_walk_needed(mplace)? {
217             return Ok(());
218         }
219 
220         if let Some(def) = mplace.layout.ty.ty_adt_def() {
221             if def.is_unsafe_cell() {
222                 // We are crossing over an `UnsafeCell`, we can mutate again. This means that
223                 // References we encounter inside here are interned as pointing to mutable
224                 // allocations.
225                 // Remember the `old` value to handle nested `UnsafeCell`.
226                 let old = std::mem::replace(&mut self.inside_unsafe_cell, true);
227                 let walked = self.walk_aggregate(mplace, fields);
228                 self.inside_unsafe_cell = old;
229                 return walked;
230             }
231         }
232 
233         self.walk_aggregate(mplace, fields)
234     }
235 
visit_value(&mut self, mplace: &MPlaceTy<'tcx>) -> InterpResult<'tcx>236     fn visit_value(&mut self, mplace: &MPlaceTy<'tcx>) -> InterpResult<'tcx> {
237         // Handle Reference types, as these are the only types with provenance supported by const eval.
238         // Raw pointers (and boxes) are handled by the `leftover_allocations` logic.
239         let tcx = self.ecx.tcx;
240         let ty = mplace.layout.ty;
241         if let ty::Ref(_, referenced_ty, ref_mutability) = *ty.kind() {
242             let value = self.ecx.read_immediate(&mplace.into())?;
243             let mplace = self.ecx.ref_to_mplace(&value)?;
244             assert_eq!(mplace.layout.ty, referenced_ty);
245             // Handle trait object vtables.
246             if let ty::Dynamic(_, _, ty::Dyn) =
247                 tcx.struct_tail_erasing_lifetimes(referenced_ty, self.ecx.param_env).kind()
248             {
249                 let ptr = mplace.meta.unwrap_meta().to_pointer(&tcx)?;
250                 if let Some(alloc_id) = ptr.provenance {
251                     // Explicitly choose const mode here, since vtables are immutable, even
252                     // if the reference of the fat pointer is mutable.
253                     self.intern_shallow(alloc_id, InternMode::Const, None);
254                 } else {
255                     // Validation will error (with a better message) on an invalid vtable pointer.
256                     // Let validation show the error message, but make sure it *does* error.
257                     tcx.sess
258                         .delay_span_bug(tcx.span, "vtables pointers cannot be integer pointers");
259                 }
260             }
261             // Check if we have encountered this pointer+layout combination before.
262             // Only recurse for allocation-backed pointers.
263             if let Some(alloc_id) = mplace.ptr.provenance {
264                 // Compute the mode with which we intern this. Our goal here is to make as many
265                 // statics as we can immutable so they can be placed in read-only memory by LLVM.
266                 let ref_mode = match self.mode {
267                     InternMode::Static(mutbl) => {
268                         // In statics, merge outer mutability with reference mutability and
269                         // take into account whether we are in an `UnsafeCell`.
270 
271                         // The only way a mutable reference actually works as a mutable reference is
272                         // by being in a `static mut` directly or behind another mutable reference.
273                         // If there's an immutable reference or we are inside a `static`, then our
274                         // mutable reference is equivalent to an immutable one. As an example:
275                         // `&&mut Foo` is semantically equivalent to `&&Foo`
276                         match ref_mutability {
277                             _ if self.inside_unsafe_cell => {
278                                 // Inside an `UnsafeCell` is like inside a `static mut`, the "outer"
279                                 // mutability does not matter.
280                                 InternMode::Static(ref_mutability)
281                             }
282                             Mutability::Not => {
283                                 // A shared reference, things become immutable.
284                                 // We do *not* consider `freeze` here: `intern_shallow` considers
285                                 // `freeze` for the actual mutability of this allocation; the intern
286                                 // mode for references contained in this allocation is tracked more
287                                 // precisely when traversing the referenced data (by tracking
288                                 // `UnsafeCell`). This makes sure that `&(&i32, &Cell<i32>)` still
289                                 // has the left inner reference interned into a read-only
290                                 // allocation.
291                                 InternMode::Static(Mutability::Not)
292                             }
293                             Mutability::Mut => {
294                                 // Mutable reference.
295                                 InternMode::Static(mutbl)
296                             }
297                         }
298                     }
299                     InternMode::Const => {
300                         // Ignore `UnsafeCell`, everything is immutable. Validity does some sanity
301                         // checking for mutable references that we encounter -- they must all be
302                         // ZST.
303                         InternMode::Const
304                     }
305                 };
306                 match self.intern_shallow(alloc_id, ref_mode, Some(referenced_ty)) {
307                     // No need to recurse, these are interned already and statics may have
308                     // cycles, so we don't want to recurse there
309                     Some(IsStaticOrFn) => {}
310                     // intern everything referenced by this value. The mutability is taken from the
311                     // reference. It is checked above that mutable references only happen in
312                     // `static mut`
313                     None => self.ref_tracking.track((mplace, ref_mode), || ()),
314                 }
315             }
316             Ok(())
317         } else {
318             // Not a reference -- proceed recursively.
319             self.walk_value(mplace)
320         }
321     }
322 }
323 
324 /// How a constant value should be interned.
325 #[derive(Copy, Clone, Debug, PartialEq, Hash, Eq)]
326 pub enum InternKind {
327     /// The `mutability` of the static, ignoring the type which may have interior mutability.
328     Static(hir::Mutability),
329     /// A `const` item
330     Constant,
331     Promoted,
332 }
333 
334 /// Intern `ret` and everything it references.
335 ///
336 /// This *cannot raise an interpreter error*. Doing so is left to validation, which
337 /// tracks where in the value we are and thus can show much better error messages.
338 #[instrument(level = "debug", skip(ecx))]
intern_const_alloc_recursive< 'mir, 'tcx: 'mir, M: CompileTimeMachine<'mir, 'tcx, const_eval::MemoryKind>, >( ecx: &mut InterpCx<'mir, 'tcx, M>, intern_kind: InternKind, ret: &MPlaceTy<'tcx>, ) -> Result<(), ErrorGuaranteed>339 pub fn intern_const_alloc_recursive<
340     'mir,
341     'tcx: 'mir,
342     M: CompileTimeMachine<'mir, 'tcx, const_eval::MemoryKind>,
343 >(
344     ecx: &mut InterpCx<'mir, 'tcx, M>,
345     intern_kind: InternKind,
346     ret: &MPlaceTy<'tcx>,
347 ) -> Result<(), ErrorGuaranteed> {
348     let tcx = ecx.tcx;
349     let base_intern_mode = match intern_kind {
350         InternKind::Static(mutbl) => InternMode::Static(mutbl),
351         // `Constant` includes array lengths.
352         InternKind::Constant | InternKind::Promoted => InternMode::Const,
353     };
354 
355     // Type based interning.
356     // `ref_tracking` tracks typed references we have already interned and still need to crawl for
357     // more typed information inside them.
358     // `leftover_allocations` collects *all* allocations we see, because some might not
359     // be available in a typed way. They get interned at the end.
360     let mut ref_tracking = RefTracking::empty();
361     let leftover_allocations = &mut FxIndexSet::default();
362 
363     // start with the outermost allocation
364     intern_shallow(
365         ecx,
366         leftover_allocations,
367         // The outermost allocation must exist, because we allocated it with
368         // `Memory::allocate`.
369         ret.ptr.provenance.unwrap(),
370         base_intern_mode,
371         Some(ret.layout.ty),
372     );
373 
374     ref_tracking.track((*ret, base_intern_mode), || ());
375 
376     while let Some(((mplace, mode), _)) = ref_tracking.todo.pop() {
377         let res = InternVisitor {
378             ref_tracking: &mut ref_tracking,
379             ecx,
380             mode,
381             leftover_allocations,
382             inside_unsafe_cell: false,
383         }
384         .visit_value(&mplace);
385         // We deliberately *ignore* interpreter errors here. When there is a problem, the remaining
386         // references are "leftover"-interned, and later validation will show a proper error
387         // and point at the right part of the value causing the problem.
388         match res {
389             Ok(()) => {}
390             Err(error) => {
391                 ecx.tcx.sess.delay_span_bug(
392                     ecx.tcx.span,
393                     format!(
394                         "error during interning should later cause validation failure: {error:?}"
395                     ),
396                 );
397             }
398         }
399     }
400 
401     // Intern the rest of the allocations as mutable. These might be inside unions, padding, raw
402     // pointers, ... So we can't intern them according to their type rules
403 
404     let mut todo: Vec<_> = leftover_allocations.iter().cloned().collect();
405     debug!(?todo);
406     debug!("dead_alloc_map: {:#?}", ecx.memory.dead_alloc_map);
407     while let Some(alloc_id) = todo.pop() {
408         if let Some((_, mut alloc)) = ecx.memory.alloc_map.remove(&alloc_id) {
409             // We can't call the `intern_shallow` method here, as its logic is tailored to safe
410             // references and a `leftover_allocations` set (where we only have a todo-list here).
411             // So we hand-roll the interning logic here again.
412             match intern_kind {
413                 // Statics may point to mutable allocations.
414                 // Even for immutable statics it would be ok to have mutable allocations behind
415                 // raw pointers, e.g. for `static FOO: *const AtomicUsize = &AtomicUsize::new(42)`.
416                 InternKind::Static(_) => {}
417                 // Raw pointers in promoteds may only point to immutable things so we mark
418                 // everything as immutable.
419                 // It is UB to mutate through a raw pointer obtained via an immutable reference:
420                 // Since all references and pointers inside a promoted must by their very definition
421                 // be created from an immutable reference (and promotion also excludes interior
422                 // mutability), mutating through them would be UB.
423                 // There's no way we can check whether the user is using raw pointers correctly,
424                 // so all we can do is mark this as immutable here.
425                 InternKind::Promoted => {
426                     // See const_eval::machine::MemoryExtra::can_access_statics for why
427                     // immutability is so important.
428                     alloc.mutability = Mutability::Not;
429                 }
430                 // If it's a constant, we should not have any "leftovers" as everything
431                 // is tracked by const-checking.
432                 // FIXME: downgrade this to a warning? It rejects some legitimate consts,
433                 // such as `const CONST_RAW: *const Vec<i32> = &Vec::new() as *const _;`.
434                 //
435                 // NOTE: it looks likes this code path is only reachable when we try to intern
436                 // something that cannot be promoted, which in constants means values that have
437                 // drop glue, such as the example above.
438                 InternKind::Constant => {
439                     ecx.tcx.sess.emit_err(UnsupportedUntypedPointer { span: ecx.tcx.span });
440                     // For better errors later, mark the allocation as immutable.
441                     alloc.mutability = Mutability::Not;
442                 }
443             }
444             let alloc = tcx.mk_const_alloc(alloc);
445             tcx.set_alloc_id_memory(alloc_id, alloc);
446             for &(_, alloc_id) in alloc.inner().provenance().ptrs().iter() {
447                 if leftover_allocations.insert(alloc_id) {
448                     todo.push(alloc_id);
449                 }
450             }
451         } else if ecx.memory.dead_alloc_map.contains_key(&alloc_id) {
452             // Codegen does not like dangling pointers, and generally `tcx` assumes that
453             // all allocations referenced anywhere actually exist. So, make sure we error here.
454             let reported = ecx.tcx.sess.emit_err(DanglingPtrInFinal { span: ecx.tcx.span });
455             return Err(reported);
456         } else if ecx.tcx.try_get_global_alloc(alloc_id).is_none() {
457             // We have hit an `AllocId` that is neither in local or global memory and isn't
458             // marked as dangling by local memory. That should be impossible.
459             span_bug!(ecx.tcx.span, "encountered unknown alloc id {:?}", alloc_id);
460         }
461     }
462     Ok(())
463 }
464 
465 impl<'mir, 'tcx: 'mir, M: super::intern::CompileTimeMachine<'mir, 'tcx, !>>
466     InterpCx<'mir, 'tcx, M>
467 {
468     /// A helper function that allocates memory for the layout given and gives you access to mutate
469     /// it. Once your own mutation code is done, the backing `Allocation` is removed from the
470     /// current `Memory` and returned.
intern_with_temp_alloc( &mut self, layout: TyAndLayout<'tcx>, f: impl FnOnce( &mut InterpCx<'mir, 'tcx, M>, &PlaceTy<'tcx, M::Provenance>, ) -> InterpResult<'tcx, ()>, ) -> InterpResult<'tcx, ConstAllocation<'tcx>>471     pub fn intern_with_temp_alloc(
472         &mut self,
473         layout: TyAndLayout<'tcx>,
474         f: impl FnOnce(
475             &mut InterpCx<'mir, 'tcx, M>,
476             &PlaceTy<'tcx, M::Provenance>,
477         ) -> InterpResult<'tcx, ()>,
478     ) -> InterpResult<'tcx, ConstAllocation<'tcx>> {
479         let dest = self.allocate(layout, MemoryKind::Stack)?;
480         f(self, &dest.into())?;
481         let mut alloc = self.memory.alloc_map.remove(&dest.ptr.provenance.unwrap()).unwrap().1;
482         alloc.mutability = Mutability::Not;
483         Ok(self.tcx.mk_const_alloc(alloc))
484     }
485 }
486