1 //! This module specifies the type based interner for constants.
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.
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.)
17 use super::validity::RefTracking;
18 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
19 use rustc_errors::ErrorReported;
21 use rustc_middle::mir::interpret::InterpResult;
22 use rustc_middle::ty::{self, layout::TyAndLayout, Ty};
24 use rustc_ast::Mutability;
26 use super::{AllocId, Allocation, InterpCx, MPlaceTy, Machine, MemoryKind, PlaceTy, ValueVisitor};
27 use crate::const_eval;
29 pub trait CompileTimeMachine<'mir, 'tcx, T> = Machine<
37 MemoryMap = FxHashMap<AllocId, (MemoryKind<T>, Allocation)>,
40 struct InternVisitor<'rt, 'mir, 'tcx, M: CompileTimeMachine<'mir, 'tcx, const_eval::MemoryKind>> {
41 /// The ectx from which we intern.
42 ecx: &'rt mut InterpCx<'mir, 'tcx, M>,
43 /// Previously encountered safe references.
44 ref_tracking: &'rt mut RefTracking<(MPlaceTy<'tcx>, InternMode)>,
45 /// A list of all encountered allocations. After type-based interning, we traverse this list to
46 /// also intern allocations that are only referenced by a raw pointer or inside a union.
47 leftover_allocations: &'rt mut FxHashSet<AllocId>,
48 /// The root kind of the value that we're looking at. This field is never mutated for a
49 /// particular allocation. It is primarily used to make as many allocations as possible
50 /// read-only so LLVM can place them in const memory.
52 /// This field stores whether we are *currently* inside an `UnsafeCell`. This can affect
53 /// the intern mode of references we encounter.
54 inside_unsafe_cell: bool,
57 #[derive(Copy, Clone, Debug, PartialEq, Hash, Eq)]
59 /// A static and its current mutability. Below shared references inside a `static mut`,
60 /// this is *immutable*, and below mutable references inside an `UnsafeCell`, this
62 Static(hir::Mutability),
67 /// Signalling data structure to ensure we don't recurse
68 /// into the memory of other constants or statics
71 /// Intern an allocation without looking at its children.
72 /// `mode` is the mode of the environment where we found this pointer.
73 /// `mutablity` is the mutability of the place to be interned; even if that says
74 /// `immutable` things might become mutable if `ty` is not frozen.
75 /// `ty` can be `None` if there is no potential interior mutability
76 /// to account for (e.g. for vtables).
77 fn intern_shallow<'rt, 'mir, 'tcx, M: CompileTimeMachine<'mir, 'tcx, const_eval::MemoryKind>>(
78 ecx: &'rt mut InterpCx<'mir, 'tcx, M>,
79 leftover_allocations: &'rt mut FxHashSet<AllocId>,
83 ) -> Option<IsStaticOrFn> {
84 trace!("intern_shallow {:?} with {:?}", alloc_id, mode);
87 let Some((kind, mut alloc)) = ecx.memory.alloc_map.remove(&alloc_id) else {
88 // Pointer not found in local memory map. It is either a pointer to the global
90 // If the pointer is dangling (neither in local nor global memory), we leave it
91 // to validation to error -- it has the much better error messages, pointing out where
92 // in the value the dangling reference lies.
93 // The `delay_span_bug` ensures that we don't forget such a check in validation.
94 if tcx.get_global_alloc(alloc_id).is_none() {
95 tcx.sess.delay_span_bug(ecx.tcx.span, "tried to intern dangling pointer");
97 // treat dangling pointers like other statics
98 // just to stop trying to recurse into them
99 return Some(IsStaticOrFn);
101 // This match is just a canary for future changes to `MemoryKind`, which most likely need
102 // changes in this function.
105 | MemoryKind::Machine(const_eval::MemoryKind::Heap)
106 | MemoryKind::CallerLocation => {}
108 // Set allocation mutability as appropriate. This is used by LLVM to put things into
109 // read-only memory, and also by Miri when evaluating other globals that
111 if let InternMode::Static(mutability) = mode {
112 // For this, we need to take into account `UnsafeCell`. When `ty` is `None`, we assume
113 // no interior mutability.
114 let frozen = ty.map_or(true, |ty| ty.is_freeze(ecx.tcx, ecx.param_env));
115 // For statics, allocation mutability is the combination of place mutability and
117 // The entire allocation needs to be mutable if it contains an `UnsafeCell` anywhere.
118 let immutable = mutability == Mutability::Not && frozen;
120 alloc.mutability = Mutability::Not;
122 // Just making sure we are not "upgrading" an immutable allocation to mutable.
123 assert_eq!(alloc.mutability, Mutability::Mut);
126 // No matter what, *constants are never mutable*. Mutating them is UB.
127 // See const_eval::machine::MemoryExtra::can_access_statics for why
128 // immutability is so important.
130 // Validation will ensure that there is no `UnsafeCell` on an immutable allocation.
131 alloc.mutability = Mutability::Not;
133 // link the alloc id to the actual allocation
134 let alloc = tcx.intern_const_alloc(alloc);
135 leftover_allocations.extend(alloc.relocations().iter().map(|&(_, alloc_id)| alloc_id));
136 tcx.set_alloc_id_memory(alloc_id, alloc);
140 impl<'rt, 'mir, 'tcx, M: CompileTimeMachine<'mir, 'tcx, const_eval::MemoryKind>>
141 InternVisitor<'rt, 'mir, 'tcx, M>
147 ty: Option<Ty<'tcx>>,
148 ) -> Option<IsStaticOrFn> {
149 intern_shallow(self.ecx, self.leftover_allocations, alloc_id, mode, ty)
153 impl<'rt, 'mir, 'tcx: 'mir, M: CompileTimeMachine<'mir, 'tcx, const_eval::MemoryKind>>
154 ValueVisitor<'mir, 'tcx, M> for InternVisitor<'rt, 'mir, 'tcx, M>
156 type V = MPlaceTy<'tcx>;
159 fn ecx(&self) -> &InterpCx<'mir, 'tcx, M> {
165 mplace: &MPlaceTy<'tcx>,
166 fields: impl Iterator<Item = InterpResult<'tcx, Self::V>>,
167 ) -> InterpResult<'tcx> {
168 // ZSTs cannot contain pointers, so we can skip them.
169 if mplace.layout.is_zst() {
173 if let Some(def) = mplace.layout.ty.ty_adt_def() {
174 if Some(def.did) == self.ecx.tcx.lang_items().unsafe_cell_type() {
175 // We are crossing over an `UnsafeCell`, we can mutate again. This means that
176 // References we encounter inside here are interned as pointing to mutable
178 // Remember the `old` value to handle nested `UnsafeCell`.
179 let old = std::mem::replace(&mut self.inside_unsafe_cell, true);
180 let walked = self.walk_aggregate(mplace, fields);
181 self.inside_unsafe_cell = old;
186 self.walk_aggregate(mplace, fields)
189 fn visit_value(&mut self, mplace: &MPlaceTy<'tcx>) -> InterpResult<'tcx> {
190 // Handle Reference types, as these are the only relocations supported by const eval.
191 // Raw pointers (and boxes) are handled by the `leftover_relocations` logic.
192 let tcx = self.ecx.tcx;
193 let ty = mplace.layout.ty;
194 if let ty::Ref(_, referenced_ty, ref_mutability) = *ty.kind() {
195 let value = self.ecx.read_immediate(&(*mplace).into())?;
196 let mplace = self.ecx.ref_to_mplace(&value)?;
197 assert_eq!(mplace.layout.ty, referenced_ty);
198 // Handle trait object vtables.
199 if let ty::Dynamic(..) =
200 tcx.struct_tail_erasing_lifetimes(referenced_ty, self.ecx.param_env).kind()
202 let ptr = self.ecx.scalar_to_ptr(mplace.meta.unwrap_meta());
203 if let Some(alloc_id) = ptr.provenance {
204 // Explicitly choose const mode here, since vtables are immutable, even
205 // if the reference of the fat pointer is mutable.
206 self.intern_shallow(alloc_id, InternMode::Const, None);
208 // Validation will error (with a better message) on an invalid vtable pointer.
209 // Let validation show the error message, but make sure it *does* error.
211 .delay_span_bug(tcx.span, "vtables pointers cannot be integer pointers");
214 // Check if we have encountered this pointer+layout combination before.
215 // Only recurse for allocation-backed pointers.
216 if let Some(alloc_id) = mplace.ptr.provenance {
217 // Compute the mode with which we intern this. Our goal here is to make as many
218 // statics as we can immutable so they can be placed in read-only memory by LLVM.
219 let ref_mode = match self.mode {
220 InternMode::Static(mutbl) => {
221 // In statics, merge outer mutability with reference mutability and
222 // take into account whether we are in an `UnsafeCell`.
224 // The only way a mutable reference actually works as a mutable reference is
225 // by being in a `static mut` directly or behind another mutable reference.
226 // If there's an immutable reference or we are inside a `static`, then our
227 // mutable reference is equivalent to an immutable one. As an example:
228 // `&&mut Foo` is semantically equivalent to `&&Foo`
229 match ref_mutability {
230 _ if self.inside_unsafe_cell => {
231 // Inside an `UnsafeCell` is like inside a `static mut`, the "outer"
232 // mutability does not matter.
233 InternMode::Static(ref_mutability)
236 // A shared reference, things become immutable.
237 // We do *not* consider `freeze` here: `intern_shallow` considers
238 // `freeze` for the actual mutability of this allocation; the intern
239 // mode for references contained in this allocation is tracked more
240 // precisely when traversing the referenced data (by tracking
241 // `UnsafeCell`). This makes sure that `&(&i32, &Cell<i32>)` still
242 // has the left inner reference interned into a read-only
244 InternMode::Static(Mutability::Not)
247 // Mutable reference.
248 InternMode::Static(mutbl)
252 InternMode::Const => {
253 // Ignore `UnsafeCell`, everything is immutable. Validity does some sanity
254 // checking for mutable references that we encounter -- they must all be
259 match self.intern_shallow(alloc_id, ref_mode, Some(referenced_ty)) {
260 // No need to recurse, these are interned already and statics may have
261 // cycles, so we don't want to recurse there
262 Some(IsStaticOrFn) => {}
263 // intern everything referenced by this value. The mutability is taken from the
264 // reference. It is checked above that mutable references only happen in
266 None => self.ref_tracking.track((mplace, ref_mode), || ()),
271 // Not a reference -- proceed recursively.
272 self.walk_value(mplace)
277 #[derive(Copy, Clone, Debug, PartialEq, Hash, Eq)]
278 pub enum InternKind {
279 /// The `mutability` of the static, ignoring the type which may have interior mutability.
280 Static(hir::Mutability),
285 /// Intern `ret` and everything it references.
287 /// This *cannot raise an interpreter error*. Doing so is left to validation, which
288 /// tracks where in the value we are and thus can show much better error messages.
289 /// Any errors here would anyway be turned into `const_err` lints, whereas validation failures
291 #[tracing::instrument(level = "debug", skip(ecx))]
292 pub fn intern_const_alloc_recursive<
295 M: CompileTimeMachine<'mir, 'tcx, const_eval::MemoryKind>,
297 ecx: &mut InterpCx<'mir, 'tcx, M>,
298 intern_kind: InternKind,
299 ret: &MPlaceTy<'tcx>,
300 ) -> Result<(), ErrorReported> {
302 let base_intern_mode = match intern_kind {
303 InternKind::Static(mutbl) => InternMode::Static(mutbl),
304 // `Constant` includes array lengths.
305 InternKind::Constant | InternKind::Promoted => InternMode::Const,
308 // Type based interning.
309 // `ref_tracking` tracks typed references we have already interned and still need to crawl for
310 // more typed information inside them.
311 // `leftover_allocations` collects *all* allocations we see, because some might not
312 // be available in a typed way. They get interned at the end.
313 let mut ref_tracking = RefTracking::empty();
314 let leftover_allocations = &mut FxHashSet::default();
316 // start with the outermost allocation
319 leftover_allocations,
320 // The outermost allocation must exist, because we allocated it with
321 // `Memory::allocate`.
322 ret.ptr.provenance.unwrap(),
327 ref_tracking.track((*ret, base_intern_mode), || ());
329 while let Some(((mplace, mode), _)) = ref_tracking.todo.pop() {
330 let res = InternVisitor {
331 ref_tracking: &mut ref_tracking,
334 leftover_allocations,
335 inside_unsafe_cell: false,
337 .visit_value(&mplace);
338 // We deliberately *ignore* interpreter errors here. When there is a problem, the remaining
339 // references are "leftover"-interned, and later validation will show a proper error
340 // and point at the right part of the value causing the problem.
344 ecx.tcx.sess.delay_span_bug(
347 "error during interning should later cause validation failure: {}",
355 // Intern the rest of the allocations as mutable. These might be inside unions, padding, raw
356 // pointers, ... So we can't intern them according to their type rules
358 let mut todo: Vec<_> = leftover_allocations.iter().cloned().collect();
359 while let Some(alloc_id) = todo.pop() {
360 if let Some((_, mut alloc)) = ecx.memory.alloc_map.remove(&alloc_id) {
361 // We can't call the `intern_shallow` method here, as its logic is tailored to safe
362 // references and a `leftover_allocations` set (where we only have a todo-list here).
363 // So we hand-roll the interning logic here again.
365 // Statics may contain mutable allocations even behind relocations.
366 // Even for immutable statics it would be ok to have mutable allocations behind
367 // raw pointers, e.g. for `static FOO: *const AtomicUsize = &AtomicUsize::new(42)`.
368 InternKind::Static(_) => {}
369 // Raw pointers in promoteds may only point to immutable things so we mark
370 // everything as immutable.
371 // It is UB to mutate through a raw pointer obtained via an immutable reference:
372 // Since all references and pointers inside a promoted must by their very definition
373 // be created from an immutable reference (and promotion also excludes interior
374 // mutability), mutating through them would be UB.
375 // There's no way we can check whether the user is using raw pointers correctly,
376 // so all we can do is mark this as immutable here.
377 InternKind::Promoted => {
378 // See const_eval::machine::MemoryExtra::can_access_statics for why
379 // immutability is so important.
380 alloc.mutability = Mutability::Not;
382 InternKind::Constant => {
383 // If it's a constant, we should not have any "leftovers" as everything
384 // is tracked by const-checking.
385 // FIXME: downgrade this to a warning? It rejects some legitimate consts,
386 // such as `const CONST_RAW: *const Vec<i32> = &Vec::new() as *const _;`.
389 .span_err(ecx.tcx.span, "untyped pointers are not allowed in constant");
390 // For better errors later, mark the allocation as immutable.
391 alloc.mutability = Mutability::Not;
394 let alloc = tcx.intern_const_alloc(alloc);
395 tcx.set_alloc_id_memory(alloc_id, alloc);
396 for &(_, alloc_id) in alloc.relocations().iter() {
397 if leftover_allocations.insert(alloc_id) {
401 } else if ecx.memory.dead_alloc_map.contains_key(&alloc_id) {
402 // Codegen does not like dangling pointers, and generally `tcx` assumes that
403 // all allocations referenced anywhere actually exist. So, make sure we error here.
404 ecx.tcx.sess.span_err(ecx.tcx.span, "encountered dangling pointer in final constant");
405 return Err(ErrorReported);
406 } else if ecx.tcx.get_global_alloc(alloc_id).is_none() {
407 // We have hit an `AllocId` that is neither in local or global memory and isn't
408 // marked as dangling by local memory. That should be impossible.
409 span_bug!(ecx.tcx.span, "encountered unknown alloc id {:?}", alloc_id);
415 impl<'mir, 'tcx: 'mir, M: super::intern::CompileTimeMachine<'mir, 'tcx, !>>
416 InterpCx<'mir, 'tcx, M>
418 /// A helper function that allocates memory for the layout given and gives you access to mutate
419 /// it. Once your own mutation code is done, the backing `Allocation` is removed from the
420 /// current `Memory` and returned.
421 pub fn intern_with_temp_alloc(
423 layout: TyAndLayout<'tcx>,
425 &mut InterpCx<'mir, 'tcx, M>,
426 &PlaceTy<'tcx, M::PointerTag>,
427 ) -> InterpResult<'tcx, ()>,
428 ) -> InterpResult<'tcx, &'tcx Allocation> {
429 let dest = self.allocate(layout, MemoryKind::Stack)?;
430 f(self, &dest.into())?;
431 let mut alloc = self.memory.alloc_map.remove(&dest.ptr.provenance.unwrap()).unwrap().1;
432 alloc.mutability = Mutability::Not;
433 Ok(self.tcx.intern_const_alloc(alloc))