3
3
//! After a const evaluation has computed a value, before we destroy the const evaluator's session
4
4
//! memory, we need to extract all memory allocations to the global memory pool so they stay around.
5
5
6
- use rustc:: ty:: { Ty , TyCtxt , ParamEnv , self } ;
6
+ use rustc:: ty:: { Ty , ParamEnv , self } ;
7
7
use rustc:: mir:: interpret:: { InterpResult , ErrorHandled } ;
8
8
use rustc:: hir;
9
9
use rustc:: hir:: def_id:: DefId ;
10
10
use super :: validity:: RefTracking ;
11
11
use rustc_data_structures:: fx:: FxHashSet ;
12
12
13
13
use syntax:: ast:: Mutability ;
14
- use syntax_pos:: Span ;
15
14
16
15
use super :: {
17
- ValueVisitor , MemoryKind , Pointer , AllocId , MPlaceTy , Scalar ,
16
+ ValueVisitor , MemoryKind , AllocId , MPlaceTy , Scalar ,
18
17
} ;
19
18
use crate :: const_eval:: { CompileTimeInterpreter , CompileTimeEvalContext } ;
20
19
@@ -27,12 +26,10 @@ struct InternVisitor<'rt, 'mir, 'tcx> {
27
26
/// for sanity assertions that will ICE when `const_qualif` screws up.
28
27
mode : InternMode ,
29
28
/// 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.
36
33
mutability : Mutability ,
37
34
/// A list of all encountered relocations. After type-based interning, we traverse this list to
38
35
/// also intern allocations that are only referenced by a raw pointer or inside a union.
@@ -45,9 +42,10 @@ enum InternMode {
45
42
/// `static`. In a `static mut` we start out as mutable and thus can also contain further `&mut`
46
43
/// that will actually be treated as mutable.
47
44
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.
49
47
ConstBase ,
50
- /// `UnsafeCell` ICEs
48
+ /// `UnsafeCell` ICEs.
51
49
Const ,
52
50
}
53
51
@@ -56,26 +54,31 @@ enum InternMode {
56
54
struct IsStaticOrFn ;
57
55
58
56
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.
60
60
fn intern_shallow (
61
61
& mut self ,
62
- ptr : Pointer ,
62
+ alloc_id : AllocId ,
63
63
mutability : Mutability ,
64
+ ty : Option < Ty < ' tcx > > ,
64
65
) -> InterpResult < ' tcx , Option < IsStaticOrFn > > {
65
66
trace ! (
66
67
"InternVisitor::intern {:?} with {:?}" ,
67
- ptr , mutability,
68
+ alloc_id , mutability,
68
69
) ;
69
70
// remove allocation
70
71
let tcx = self . ecx . tcx ;
71
72
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) {
73
74
Some ( entry) => entry,
74
75
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
76
79
// to validation to error. The `delay_span_bug` ensures that we don't forget such
77
80
// 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 ( ) {
79
82
tcx. sess . delay_span_bug ( self . ecx . tcx . span , "tried to intern dangling pointer" ) ;
80
83
}
81
84
// treat dangling pointers like other statics
@@ -88,14 +91,35 @@ impl<'rt, 'mir, 'tcx> InternVisitor<'rt, 'mir, 'tcx> {
88
91
match kind {
89
92
MemoryKind :: Stack | MemoryKind :: Vtable => { } ,
90
93
}
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
+ } ;
95
119
// link the alloc id to the actual allocation
96
120
let alloc = tcx. intern_const_alloc ( alloc) ;
97
121
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) ;
99
123
Ok ( None )
100
124
}
101
125
}
@@ -119,14 +143,16 @@ for
119
143
) -> InterpResult < ' tcx > {
120
144
if let Some ( def) = mplace. layout . ty . ty_adt_def ( ) {
121
145
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.
123
149
let old = std:: mem:: replace ( & mut self . mutability , Mutability :: Mutable ) ;
124
150
assert_ne ! (
125
151
self . mode, InternMode :: Const ,
126
152
"UnsafeCells are not allowed behind references in constants. This should have \
127
153
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 .",
130
156
) ;
131
157
let walked = self . walk_aggregate ( mplace, fields) ;
132
158
self . mutability = old;
150
176
if let Ok ( vtable) = meta. unwrap ( ) . to_ptr ( ) {
151
177
// explitly choose `Immutable` here, since vtables are immutable, even
152
178
// 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 ) ?;
154
180
}
155
181
}
156
182
}
@@ -195,21 +221,13 @@ for
195
221
( Mutability :: Mutable , hir:: Mutability :: MutMutable ) => Mutability :: Mutable ,
196
222
_ => Mutability :: Immutable ,
197
223
} ;
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
- ) ;
206
224
// Recursing behind references changes the intern mode for constants in order to
207
225
// cause assertions to trigger if we encounter any `UnsafeCell`s.
208
226
let mode = match self . mode {
209
227
InternMode :: ConstBase => InternMode :: Const ,
210
228
other => other,
211
229
} ;
212
- match self . intern_shallow ( ptr, intern_mutability ) ? {
230
+ match self . intern_shallow ( ptr. alloc_id , mutability , Some ( mplace . layout . ty ) ) ? {
213
231
// No need to recurse, these are interned already and statics may have
214
232
// cycles, so we don't want to recurse there
215
233
Some ( IsStaticOrFn ) => { } ,
224
242
}
225
243
}
226
244
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
-
244
245
pub fn intern_const_alloc_recursive (
245
246
ecx : & mut CompileTimeEvalContext < ' mir , ' tcx > ,
246
247
def_id : DefId ,
@@ -251,34 +252,26 @@ pub fn intern_const_alloc_recursive(
251
252
) -> InterpResult < ' tcx > {
252
253
let tcx = ecx. tcx ;
253
254
// 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) {
255
256
Some ( hir:: Mutability :: MutImmutable ) => ( Mutability :: Immutable , InternMode :: Static ) ,
256
257
None => ( Mutability :: Immutable , InternMode :: ConstBase ) ,
257
258
// `static mut` doesn't care about interior mutability, it's mutable anyway
258
259
Some ( hir:: Mutability :: MutMutable ) => ( Mutability :: Mutable , InternMode :: Static ) ,
259
260
} ;
260
261
261
262
// 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) ) ;
263
264
let leftover_relocations = & mut FxHashSet :: default ( ) ;
264
265
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
-
273
266
// start with the outermost allocation
274
267
InternVisitor {
275
268
ref_tracking : & mut ref_tracking,
276
269
ecx,
277
270
mode : base_intern_mode,
278
271
leftover_relocations,
279
272
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 ) ) ?;
282
275
283
276
while let Some ( ( ( mplace, mutability, mode) , _) ) = ref_tracking. todo . pop ( ) {
284
277
let interned = InternVisitor {
@@ -312,8 +305,8 @@ pub fn intern_const_alloc_recursive(
312
305
let mut todo: Vec < _ > = leftover_relocations. iter ( ) . cloned ( ) . collect ( ) ;
313
306
while let Some ( alloc_id) = todo. pop ( ) {
314
307
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.
317
310
let alloc = tcx. intern_const_alloc ( alloc) ;
318
311
tcx. alloc_map . lock ( ) . set_alloc_id_memory ( alloc_id, alloc) ;
319
312
for & ( _, ( ( ) , reloc) ) in alloc. relocations ( ) . iter ( ) {
0 commit comments