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 use super::validity::RefTracking;
8 use rustc::mir::interpret::{ErrorHandled, InterpResult};
9 use rustc::ty::{self, Ty};
10 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
12 use syntax::ast::Mutability;
15 AllocId, Allocation, InterpCx, Machine, MemoryKind, MPlaceTy, Scalar, ValueVisitor,
18 pub trait CompileTimeMachine<'mir, 'tcx> =
28 MemoryMap = FxHashMap<AllocId, (MemoryKind<!>, Allocation)>,
31 struct InternVisitor<'rt, 'mir, 'tcx, M: CompileTimeMachine<'mir, 'tcx>> {
32 /// The ectx from which we intern.
33 ecx: &'rt mut InterpCx<'mir, 'tcx, M>,
34 /// Previously encountered safe references.
35 ref_tracking: &'rt mut RefTracking<(MPlaceTy<'tcx>, Mutability, InternMode)>,
36 /// A list of all encountered allocations. After type-based interning, we traverse this list to
37 /// also intern allocations that are only referenced by a raw pointer or inside a union.
38 leftover_allocations: &'rt mut FxHashSet<AllocId>,
39 /// The root node of the value that we're looking at. This field is never mutated and only used
40 /// for sanity assertions that will ICE when `const_qualif` screws up.
42 /// This field stores the mutability of the value *currently* being checked.
43 /// When encountering a mutable reference, we determine the pointee mutability
44 /// taking into account the mutability of the context: `& &mut i32` is entirely immutable,
45 /// despite the nested mutable reference!
46 /// The field gets updated when an `UnsafeCell` is encountered.
47 mutability: Mutability,
50 #[derive(Copy, Clone, Debug, PartialEq, Hash, Eq)]
52 /// Mutable references must in fact be immutable due to their surrounding immutability in a
53 /// `static`. In a `static mut` we start out as mutable and thus can also contain further `&mut`
54 /// that will actually be treated as mutable.
56 /// UnsafeCell is OK in the value of a constant: `const FOO = Cell::new(0)` creates
57 /// a new cell every time it is used.
59 /// `UnsafeCell` ICEs.
63 /// Signalling data structure to ensure we don't recurse
64 /// into the memory of other constants or statics
67 /// Intern an allocation without looking at its children.
68 /// `mode` is the mode of the environment where we found this pointer.
69 /// `mutablity` is the mutability of the place to be interned; even if that says
70 /// `immutable` things might become mutable if `ty` is not frozen.
71 /// `ty` can be `None` if there is no potential interior mutability
72 /// to account for (e.g. for vtables).
73 fn intern_shallow<'rt, 'mir, 'tcx, M: CompileTimeMachine<'mir, 'tcx>>(
74 ecx: &'rt mut InterpCx<'mir, 'tcx, M>,
75 leftover_allocations: &'rt mut FxHashSet<AllocId>,
78 mutability: Mutability,
80 ) -> InterpResult<'tcx, Option<IsStaticOrFn>> {
81 trace!("InternVisitor::intern {:?} with {:?}", alloc_id, mutability,);
84 let (kind, mut alloc) = match ecx.memory.alloc_map.remove(&alloc_id) {
87 // Pointer not found in local memory map. It is either a pointer to the global
89 // If the pointer is dangling (neither in local nor global memory), we leave it
90 // to validation to error. The `delay_span_bug` ensures that we don't forget such
91 // a check in validation.
92 if tcx.alloc_map.lock().get(alloc_id).is_none() {
93 tcx.sess.delay_span_bug(ecx.tcx.span, "tried to intern dangling pointer");
95 // treat dangling pointers like other statics
96 // just to stop trying to recurse into them
97 return Ok(Some(IsStaticOrFn));
100 // This match is just a canary for future changes to `MemoryKind`, which most likely need
101 // changes in this function.
103 MemoryKind::Stack | MemoryKind::Vtable | MemoryKind::CallerLocation => {},
105 // Set allocation mutability as appropriate. This is used by LLVM to put things into
106 // read-only memory, and also by Miri when evluating other constants/statics that
108 if mode == InternMode::Static {
109 // When `ty` is `None`, we assume no interior mutability.
110 let frozen = ty.map_or(true, |ty| ty.is_freeze(
115 // For statics, allocation mutability is the combination of the place mutability and
116 // the type mutability.
117 // The entire allocation needs to be mutable if it contains an `UnsafeCell` anywhere.
118 if mutability == Mutability::Not && frozen {
119 alloc.mutability = Mutability::Not;
121 // Just making sure we are not "upgrading" an immutable allocation to mutable.
122 assert_eq!(alloc.mutability, Mutability::Mut);
125 // We *could* be non-frozen at `ConstBase`, for constants like `Cell::new(0)`.
126 // But we still intern that as immutable as the memory cannot be changed once the
127 // initial value was computed.
128 // Constants are never mutable.
130 mutability, Mutability::Not,
131 "Something went very wrong: mutability requested for a constant"
133 alloc.mutability = Mutability::Not;
135 // link the alloc id to the actual allocation
136 let alloc = tcx.intern_const_alloc(alloc);
137 leftover_allocations.extend(alloc.relocations().iter().map(|&(_, ((), reloc))| reloc));
138 tcx.alloc_map.lock().set_alloc_id_memory(alloc_id, alloc);
142 impl<'rt, 'mir, 'tcx, M: CompileTimeMachine<'mir, 'tcx>> InternVisitor<'rt, 'mir, 'tcx, M> {
146 mutability: Mutability,
147 ty: Option<Ty<'tcx>>,
148 ) -> InterpResult<'tcx, Option<IsStaticOrFn>> {
151 self.leftover_allocations,
160 impl<'rt, 'mir, 'tcx, M: CompileTimeMachine<'mir, 'tcx>>
161 ValueVisitor<'mir, 'tcx, M>
163 InternVisitor<'rt, 'mir, 'tcx, M>
165 type V = MPlaceTy<'tcx>;
168 fn ecx(&self) -> &InterpCx<'mir, 'tcx, M> {
174 mplace: MPlaceTy<'tcx>,
175 fields: impl Iterator<Item=InterpResult<'tcx, Self::V>>,
176 ) -> InterpResult<'tcx> {
177 if let Some(def) = mplace.layout.ty.ty_adt_def() {
178 if Some(def.did) == self.ecx.tcx.lang_items().unsafe_cell_type() {
179 // We are crossing over an `UnsafeCell`, we can mutate again. This means that
180 // References we encounter inside here are interned as pointing to mutable
182 let old = std::mem::replace(&mut self.mutability, Mutability::Mut);
184 self.mode, InternMode::Const,
185 "UnsafeCells are not allowed behind references in constants. This should have \
186 been prevented statically by const qualification. If this were allowed one \
187 would be able to change a constant at one use site and other use sites could \
188 observe that mutation.",
190 let walked = self.walk_aggregate(mplace, fields);
191 self.mutability = old;
195 self.walk_aggregate(mplace, fields)
198 fn visit_primitive(&mut self, mplace: MPlaceTy<'tcx>) -> InterpResult<'tcx> {
199 // Handle Reference types, as these are the only relocations supported by const eval.
200 // Raw pointers (and boxes) are handled by the `leftover_relocations` logic.
201 let ty = mplace.layout.ty;
202 if let ty::Ref(_, referenced_ty, mutability) = ty.kind {
203 let value = self.ecx.read_immediate(mplace.into())?;
204 let mplace = self.ecx.ref_to_mplace(value)?;
205 // Handle trait object vtables
206 if let ty::Dynamic(..) =
207 self.ecx.tcx.struct_tail_erasing_lifetimes(
208 referenced_ty, self.ecx.param_env).kind
210 if let Ok(vtable) = mplace.meta.unwrap().to_ptr() {
211 // explitly choose `Immutable` here, since vtables are immutable, even
212 // if the reference of the fat pointer is mutable
213 self.intern_shallow(vtable.alloc_id, Mutability::Not, None)?;
216 // Check if we have encountered this pointer+layout combination before.
217 // Only recurse for allocation-backed pointers.
218 if let Scalar::Ptr(ptr) = mplace.ptr {
219 // We do not have any `frozen` logic here, because it's essentially equivalent to
220 // the mutability except for the outermost item. Only `UnsafeCell` can "unfreeze",
221 // and we check that in `visit_aggregate`.
222 // This is not an inherent limitation, but one that we know to be true, because
223 // const qualification enforces it. We can lift it in the future.
224 match (self.mode, mutability) {
225 // immutable references are fine everywhere
226 (_, hir::Mutability::Not) => {},
227 // all is "good and well" in the unsoundness of `static mut`
229 // mutable references are ok in `static`. Either they are treated as immutable
230 // because they are behind an immutable one, or they are behind an `UnsafeCell`
232 (InternMode::Static, hir::Mutability::Mut) => {},
233 // we statically prevent `&mut T` via `const_qualif` and double check this here
234 (InternMode::ConstBase, hir::Mutability::Mut) |
235 (InternMode::Const, hir::Mutability::Mut) => {
236 match referenced_ty.kind {
238 if n.eval_usize(self.ecx.tcx.tcx, self.ecx.param_env) == 0 => {}
240 if mplace.meta.unwrap().to_machine_usize(self.ecx)? == 0 => {}
241 _ => bug!("const qualif failed to prevent mutable references"),
245 // Compute the mutability with which we'll start visiting the allocation. This is
246 // what gets changed when we encounter an `UnsafeCell`.
248 // The only way a mutable reference actually works as a mutable reference is
249 // by being in a `static mut` directly or behind another mutable reference.
250 // If there's an immutable reference or we are inside a static, then our
251 // mutable reference is equivalent to an immutable one. As an example:
252 // `&&mut Foo` is semantically equivalent to `&&Foo`
253 let mutability = self.mutability.and(mutability);
254 // Recursing behind references changes the intern mode for constants in order to
255 // cause assertions to trigger if we encounter any `UnsafeCell`s.
256 let mode = match self.mode {
257 InternMode::ConstBase => InternMode::Const,
260 match self.intern_shallow(ptr.alloc_id, mutability, Some(mplace.layout.ty))? {
261 // No need to recurse, these are interned already and statics may have
262 // cycles, so we don't want to recurse there
263 Some(IsStaticOrFn) => {},
264 // intern everything referenced by this value. The mutability is taken from the
265 // reference. It is checked above that mutable references only happen in
267 None => self.ref_tracking.track((mplace, mutability, mode), || ()),
275 pub fn intern_const_alloc_recursive<M: CompileTimeMachine<'mir, 'tcx>>(
276 ecx: &mut InterpCx<'mir, 'tcx, M>,
277 // The `mutability` of the place, ignoring the type.
278 place_mut: Option<hir::Mutability>,
280 ) -> InterpResult<'tcx> {
282 let (base_mutability, base_intern_mode) = match place_mut {
283 // `static mut` doesn't care about interior mutability, it's mutable anyway
284 Some(mutbl) => (mutbl, InternMode::Static),
285 // consts, promoteds. FIXME: what about array lengths, array initializers?
286 None => (Mutability::Not, InternMode::ConstBase),
289 // Type based interning.
290 // `ref_tracking` tracks typed references we have seen and still need to crawl for
291 // more typed information inside them.
292 // `leftover_allocations` collects *all* allocations we see, because some might not
293 // be available in a typed way. They get interned at the end.
294 let mut ref_tracking = RefTracking::new((ret, base_mutability, base_intern_mode));
295 let leftover_allocations = &mut FxHashSet::default();
297 // start with the outermost allocation
300 leftover_allocations,
302 ret.ptr.to_ptr()?.alloc_id,
307 while let Some(((mplace, mutability, mode), _)) = ref_tracking.todo.pop() {
308 let interned = InternVisitor {
309 ref_tracking: &mut ref_tracking,
312 leftover_allocations,
314 }.visit_value(mplace);
315 if let Err(error) = interned {
316 // This can happen when e.g. the tag of an enum is not a valid discriminant. We do have
317 // to read enum discriminants in order to find references in enum variant fields.
318 if let err_unsup!(ValidationFailure(_)) = error.kind {
319 let err = crate::const_eval::error_to_const_error(&ecx, error);
320 match err.struct_error(ecx.tcx, "it is undefined behavior to use this value") {
322 diag.note(crate::const_eval::note_on_undefined_behavior_error());
325 Err(ErrorHandled::TooGeneric) |
326 Err(ErrorHandled::Reported) => {},
332 // Intern the rest of the allocations as mutable. These might be inside unions, padding, raw
333 // pointers, ... So we can't intern them according to their type rules
335 let mut todo: Vec<_> = leftover_allocations.iter().cloned().collect();
336 while let Some(alloc_id) = todo.pop() {
337 if let Some((_, mut alloc)) = ecx.memory.alloc_map.remove(&alloc_id) {
338 // We can't call the `intern_shallow` method here, as its logic is tailored to safe
339 // references and a `leftover_allocations` set (where we only have a todo-list here).
340 // So we hand-roll the interning logic here again.
341 if base_intern_mode != InternMode::Static {
342 // If it's not a static, it *must* be immutable.
343 // We cannot have mutable memory inside a constant.
344 // FIXME: ideally we would assert that they already are immutable, to double-
345 // check our static checks.
346 alloc.mutability = Mutability::Not;
348 let alloc = tcx.intern_const_alloc(alloc);
349 tcx.alloc_map.lock().set_alloc_id_memory(alloc_id, alloc);
350 for &(_, ((), reloc)) in alloc.relocations().iter() {
351 if leftover_allocations.insert(reloc) {
355 } else if ecx.memory.dead_alloc_map.contains_key(&alloc_id) {
357 throw_unsup!(ValidationFailure("encountered dangling pointer in final constant".into()))