Skip to content

Commit 6137691

Browse files
committed
const interning: move mutability computation into intern_shallow, and always intern constants as immutable
1 parent 8bf776d commit 6137691

File tree

2 files changed

+60
-67
lines changed

2 files changed

+60
-67
lines changed

src/librustc_mir/interpret/intern.rs

Lines changed: 59 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,17 @@
33
//! After a const evaluation has computed a value, before we destroy the const evaluator's session
44
//! memory, we need to extract all memory allocations to the global memory pool so they stay around.
55
6-
use rustc::ty::{Ty, TyCtxt, ParamEnv, self};
6+
use rustc::ty::{Ty, ParamEnv, self};
77
use rustc::mir::interpret::{InterpResult, ErrorHandled};
88
use rustc::hir;
99
use rustc::hir::def_id::DefId;
1010
use super::validity::RefTracking;
1111
use rustc_data_structures::fx::FxHashSet;
1212

1313
use syntax::ast::Mutability;
14-
use syntax_pos::Span;
1514

1615
use super::{
17-
ValueVisitor, MemoryKind, Pointer, AllocId, MPlaceTy, Scalar,
16+
ValueVisitor, MemoryKind, AllocId, MPlaceTy, Scalar,
1817
};
1918
use crate::const_eval::{CompileTimeInterpreter, CompileTimeEvalContext};
2019

@@ -27,12 +26,10 @@ struct InternVisitor<'rt, 'mir, 'tcx> {
2726
/// for sanity assertions that will ICE when `const_qualif` screws up.
2827
mode: InternMode,
2928
/// This field stores the mutability of the value *currently* being checked.
30-
/// It is set to mutable when an `UnsafeCell` is encountered
31-
/// When recursing across a reference, we don't recurse but store the
32-
/// value to be checked in `ref_tracking` together with the mutability at which we are checking
33-
/// the value.
34-
/// When encountering an immutable reference, we treat everything as immutable that is behind
35-
/// it.
29+
/// When encountering a mutable reference, we determine the pointee mutability
30+
/// taking into account the mutability of the context: `& &mut i32` is entirely immutable,
31+
/// despite the nested mutable reference!
32+
/// The field gets updated when an `UnsafeCell` is encountered.
3633
mutability: Mutability,
3734
/// A list of all encountered relocations. After type-based interning, we traverse this list to
3835
/// also intern allocations that are only referenced by a raw pointer or inside a union.
@@ -45,9 +42,10 @@ enum InternMode {
4542
/// `static`. In a `static mut` we start out as mutable and thus can also contain further `&mut`
4643
/// that will actually be treated as mutable.
4744
Static,
48-
/// UnsafeCell is OK in the value of a constant, but not behind references in a constant
45+
/// UnsafeCell is OK in the value of a constant: `const FOO = Cell::new(0)` creates
46+
/// a new cell every time it is used.
4947
ConstBase,
50-
/// `UnsafeCell` ICEs
48+
/// `UnsafeCell` ICEs.
5149
Const,
5250
}
5351

@@ -56,26 +54,31 @@ enum InternMode {
5654
struct IsStaticOrFn;
5755

5856
impl<'rt, 'mir, 'tcx> InternVisitor<'rt, 'mir, 'tcx> {
59-
/// Intern an allocation without looking at its children
57+
/// Intern an allocation without looking at its children.
58+
/// `mutablity` is the mutability of the place to be interned; even if that says
59+
/// `immutable` things might become mutable if `ty` is not frozen.
6060
fn intern_shallow(
6161
&mut self,
62-
ptr: Pointer,
62+
alloc_id: AllocId,
6363
mutability: Mutability,
64+
ty: Option<Ty<'tcx>>,
6465
) -> InterpResult<'tcx, Option<IsStaticOrFn>> {
6566
trace!(
6667
"InternVisitor::intern {:?} with {:?}",
67-
ptr, mutability,
68+
alloc_id, mutability,
6869
);
6970
// remove allocation
7071
let tcx = self.ecx.tcx;
7172
let memory = self.ecx.memory_mut();
72-
let (kind, mut alloc) = match memory.alloc_map.remove(&ptr.alloc_id) {
73+
let (kind, mut alloc) = match memory.alloc_map.remove(&alloc_id) {
7374
Some(entry) => entry,
7475
None => {
75-
// if the pointer is dangling (neither in local nor global memory), we leave it
76+
// Pointer not found in local memory map. It is either a pointer to the global
77+
// map, or dangling.
78+
// If the pointer is dangling (neither in local nor global memory), we leave it
7679
// to validation to error. The `delay_span_bug` ensures that we don't forget such
7780
// a check in validation.
78-
if tcx.alloc_map.lock().get(ptr.alloc_id).is_none() {
81+
if tcx.alloc_map.lock().get(alloc_id).is_none() {
7982
tcx.sess.delay_span_bug(self.ecx.tcx.span, "tried to intern dangling pointer");
8083
}
8184
// treat dangling pointers like other statics
@@ -88,14 +91,35 @@ impl<'rt, 'mir, 'tcx> InternVisitor<'rt, 'mir, 'tcx> {
8891
match kind {
8992
MemoryKind::Stack | MemoryKind::Vtable => {},
9093
}
91-
// Ensure llvm knows to only put this into immutable memory if the value is immutable either
92-
// by being behind a reference or by being part of a static or const without interior
93-
// mutability
94-
alloc.mutability = mutability;
94+
// Set allocation mutability as appropriate. This is used by LLVM to put things into
95+
// read-only memory, and also by Miri when evluating other constants/statics that
96+
// access this one.
97+
if self.mode == InternMode::Static {
98+
let frozen = ty.map_or(true, |ty| ty.is_freeze(
99+
self.ecx.tcx.tcx,
100+
self.param_env,
101+
self.ecx.tcx.span,
102+
));
103+
// For statics, allocation mutability is the combination of the place mutability and
104+
// the type mutability.
105+
// The entire allocation needs to be mutable if it contains an `UnsafeCell` anywhere.
106+
if mutability == Mutability::Immutable && frozen {
107+
alloc.mutability = Mutability::Immutable;
108+
} else {
109+
// Just making sure we are not "upgrading" an immutable allocation to mutable.
110+
assert_eq!(alloc.mutability, Mutability::Mutable);
111+
}
112+
} else {
113+
// We *could* be non-frozen at `ConstBase`, for constants like `Cell::new(0)`.
114+
// But we still intern that as immutable as the memory cannot be changed once the
115+
// initial value was computed.
116+
// Constants are never mutable.
117+
alloc.mutability = Mutability::Immutable;
118+
};
95119
// link the alloc id to the actual allocation
96120
let alloc = tcx.intern_const_alloc(alloc);
97121
self.leftover_relocations.extend(alloc.relocations().iter().map(|&(_, ((), reloc))| reloc));
98-
tcx.alloc_map.lock().set_alloc_id_memory(ptr.alloc_id, alloc);
122+
tcx.alloc_map.lock().set_alloc_id_memory(alloc_id, alloc);
99123
Ok(None)
100124
}
101125
}
@@ -119,14 +143,16 @@ for
119143
) -> InterpResult<'tcx> {
120144
if let Some(def) = mplace.layout.ty.ty_adt_def() {
121145
if Some(def.did) == self.ecx.tcx.lang_items().unsafe_cell_type() {
122-
// We are crossing over an `UnsafeCell`, we can mutate again
146+
// We are crossing over an `UnsafeCell`, we can mutate again. This means that
147+
// References we encounter inside here are interned as pointing to mutable
148+
// allocations.
123149
let old = std::mem::replace(&mut self.mutability, Mutability::Mutable);
124150
assert_ne!(
125151
self.mode, InternMode::Const,
126152
"UnsafeCells are not allowed behind references in constants. This should have \
127153
been prevented statically by const qualification. If this were allowed one \
128-
would be able to change a constant at one use site and other use sites may \
129-
arbitrarily decide to change, too.",
154+
would be able to change a constant at one use site and other use sites could \
155+
observe that mutation.",
130156
);
131157
let walked = self.walk_aggregate(mplace, fields);
132158
self.mutability = old;
@@ -150,7 +176,7 @@ for
150176
if let Ok(vtable) = meta.unwrap().to_ptr() {
151177
// explitly choose `Immutable` here, since vtables are immutable, even
152178
// if the reference of the fat pointer is mutable
153-
self.intern_shallow(vtable, Mutability::Immutable)?;
179+
self.intern_shallow(vtable.alloc_id, Mutability::Immutable, None)?;
154180
}
155181
}
156182
}
@@ -195,21 +221,13 @@ for
195221
(Mutability::Mutable, hir::Mutability::MutMutable) => Mutability::Mutable,
196222
_ => Mutability::Immutable,
197223
};
198-
// Compute the mutability of the allocation
199-
let intern_mutability = intern_mutability(
200-
self.ecx.tcx.tcx,
201-
self.param_env,
202-
mplace.layout.ty,
203-
self.ecx.tcx.span,
204-
mutability,
205-
);
206224
// Recursing behind references changes the intern mode for constants in order to
207225
// cause assertions to trigger if we encounter any `UnsafeCell`s.
208226
let mode = match self.mode {
209227
InternMode::ConstBase => InternMode::Const,
210228
other => other,
211229
};
212-
match self.intern_shallow(ptr, intern_mutability)? {
230+
match self.intern_shallow(ptr.alloc_id, mutability, Some(mplace.layout.ty))? {
213231
// No need to recurse, these are interned already and statics may have
214232
// cycles, so we don't want to recurse there
215233
Some(IsStaticOrFn) => {},
@@ -224,23 +242,6 @@ for
224242
}
225243
}
226244

227-
/// Figure out the mutability of the allocation.
228-
/// Mutable if it has interior mutability *anywhere* in the type.
229-
fn intern_mutability<'tcx>(
230-
tcx: TyCtxt<'tcx>,
231-
param_env: ParamEnv<'tcx>,
232-
ty: Ty<'tcx>,
233-
span: Span,
234-
mutability: Mutability,
235-
) -> Mutability {
236-
let has_interior_mutability = !ty.is_freeze(tcx, param_env, span);
237-
if has_interior_mutability {
238-
Mutability::Mutable
239-
} else {
240-
mutability
241-
}
242-
}
243-
244245
pub fn intern_const_alloc_recursive(
245246
ecx: &mut CompileTimeEvalContext<'mir, 'tcx>,
246247
def_id: DefId,
@@ -251,34 +252,26 @@ pub fn intern_const_alloc_recursive(
251252
) -> InterpResult<'tcx> {
252253
let tcx = ecx.tcx;
253254
// this `mutability` is the mutability of the place, ignoring the type
254-
let (mutability, base_intern_mode) = match tcx.static_mutability(def_id) {
255+
let (base_mutability, base_intern_mode) = match tcx.static_mutability(def_id) {
255256
Some(hir::Mutability::MutImmutable) => (Mutability::Immutable, InternMode::Static),
256257
None => (Mutability::Immutable, InternMode::ConstBase),
257258
// `static mut` doesn't care about interior mutability, it's mutable anyway
258259
Some(hir::Mutability::MutMutable) => (Mutability::Mutable, InternMode::Static),
259260
};
260261

261262
// type based interning
262-
let mut ref_tracking = RefTracking::new((ret, mutability, base_intern_mode));
263+
let mut ref_tracking = RefTracking::new((ret, base_mutability, base_intern_mode));
263264
let leftover_relocations = &mut FxHashSet::default();
264265

265-
// This mutability is the combination of the place mutability and the type mutability. If either
266-
// is mutable, `alloc_mutability` is mutable. This exists because the entire allocation needs
267-
// to be mutable if it contains an `UnsafeCell` anywhere. The other `mutability` exists so that
268-
// the visitor does not treat everything outside the `UnsafeCell` as mutable.
269-
let alloc_mutability = intern_mutability(
270-
tcx.tcx, param_env, ret.layout.ty, tcx.span, mutability,
271-
);
272-
273266
// start with the outermost allocation
274267
InternVisitor {
275268
ref_tracking: &mut ref_tracking,
276269
ecx,
277270
mode: base_intern_mode,
278271
leftover_relocations,
279272
param_env,
280-
mutability,
281-
}.intern_shallow(ret.ptr.to_ptr()?, alloc_mutability)?;
273+
mutability: base_mutability,
274+
}.intern_shallow(ret.ptr.to_ptr()?.alloc_id, base_mutability, Some(ret.layout.ty))?;
282275

283276
while let Some(((mplace, mutability, mode), _)) = ref_tracking.todo.pop() {
284277
let interned = InternVisitor {
@@ -312,8 +305,8 @@ pub fn intern_const_alloc_recursive(
312305
let mut todo: Vec<_> = leftover_relocations.iter().cloned().collect();
313306
while let Some(alloc_id) = todo.pop() {
314307
if let Some((_, alloc)) = ecx.memory_mut().alloc_map.remove(&alloc_id) {
315-
// We can't call the `intern` method here, as its logic is tailored to safe references.
316-
// So we hand-roll the interning logic here again
308+
// We can't call the `intern_shallow` method here, as its logic is tailored to safe
309+
// references. So we hand-roll the interning logic here again.
317310
let alloc = tcx.intern_const_alloc(alloc);
318311
tcx.alloc_map.lock().set_alloc_id_memory(alloc_id, alloc);
319312
for &(_, ((), reloc)) in alloc.relocations().iter() {

src/test/ui/consts/miri_unleashed/mutable_references_ice.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ LL | *MUH.x.get() = 99;
66

77
thread 'rustc' panicked at 'assertion failed: `(left != right)`
88
left: `Const`,
9-
right: `Const`: UnsafeCells are not allowed behind references in constants. This should have been prevented statically by const qualification. If this were allowed one would be able to change a constant at one use site and other use sites may arbitrarily decide to change, too.', src/librustc_mir/interpret/intern.rs:LL:CC
9+
right: `Const`: UnsafeCells are not allowed behind references in constants. This should have been prevented statically by const qualification. If this were allowed one would be able to change a constant at one use site and other use sites could observe that mutation.', src/librustc_mir/interpret/intern.rs:LL:CC
1010
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
1111

1212
error: internal compiler error: unexpected panic

0 commit comments

Comments
 (0)