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 } ;
77use rustc:: mir:: interpret:: { InterpResult , ErrorHandled } ;
88use rustc:: hir;
99use rustc:: hir:: def_id:: DefId ;
1010use super :: validity:: RefTracking ;
1111use rustc_data_structures:: fx:: FxHashSet ;
1212
1313use syntax:: ast:: Mutability ;
14- use syntax_pos:: Span ;
1514
1615use super :: {
17- ValueVisitor , MemoryKind , Pointer , AllocId , MPlaceTy , Scalar ,
16+ ValueVisitor , MemoryKind , AllocId , MPlaceTy , Scalar ,
1817} ;
1918use 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 {
5654struct IsStaticOrFn ;
5755
5856impl < ' 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;
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 ) => { } ,
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-
244245pub 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 ( ) {
0 commit comments