5
5
//!
6
6
//! In principle, this is not very complicated: we recursively walk the final value, follow all the
7
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.)
8
+ //! is picking the right mutability: the outermost allocation generally has a clear mutability, but
9
+ //! what about the other allocations it points to that have also been created with this value?
10
+ //! Here the rules are: `static`, `const`, and promoted can only create immutable allocations
11
+ //! that way. `static mut` can be initialized with expressions like `&mut 42`, so all inner
12
+ //! allocations are marked mutable. Some of them could potentially be made immutable, but that
13
+ //! would require relying on type information, and given how many ways Rust has to lie about
14
+ //! type information, we want to avoid doing that.
16
15
17
16
use super :: validity:: RefTracking ;
18
17
use rustc_data_structures:: fx:: { FxIndexMap , FxIndexSet } ;
19
18
use rustc_errors:: ErrorGuaranteed ;
20
19
use rustc_hir as hir;
21
20
use rustc_middle:: mir:: interpret:: InterpResult ;
22
- use rustc_middle:: ty:: { self , layout:: TyAndLayout , Ty } ;
21
+ use rustc_middle:: ty:: { self , layout:: TyAndLayout } ;
23
22
24
23
use rustc_ast:: Mutability ;
25
24
@@ -56,6 +55,8 @@ struct InternVisitor<'rt, 'mir, 'tcx, M: CompileTimeMachine<'mir, 'tcx, const_ev
56
55
/// This field stores whether we are *currently* inside an `UnsafeCell`. This can affect
57
56
/// the intern mode of references we encounter.
58
57
inside_unsafe_cell: bool ,
58
+ /// The mutability with which to intern the pointers we find.
59
+ intern_mutability: Mutability ,
59
60
}
60
61
61
62
#[ derive( Copy , Clone , Debug , PartialEq , Hash , Eq ) ]
@@ -82,10 +83,9 @@ fn intern_shallow<'rt, 'mir, 'tcx, M: CompileTimeMachine<'mir, 'tcx, const_eval:
82
83
ecx : & ' rt mut InterpCx < ' mir , ' tcx , M > ,
83
84
leftover_allocations : & ' rt mut FxIndexSet < AllocId > ,
84
85
alloc_id : AllocId ,
85
- mode : InternMode ,
86
- ty : Option < Ty < ' tcx > > ,
86
+ mutability : Mutability ,
87
87
) -> Option < IsStaticOrFn > {
88
- trace ! ( "intern_shallow {:?} with {:?} " , alloc_id, mode ) ;
88
+ trace ! ( "intern_shallow {:?}" , alloc_id) ;
89
89
// remove allocation
90
90
let tcx = ecx. tcx ;
91
91
let Some ( ( kind, mut alloc) ) = ecx. memory . alloc_map . remove ( & alloc_id) else {
@@ -112,28 +112,15 @@ fn intern_shallow<'rt, 'mir, 'tcx, M: CompileTimeMachine<'mir, 'tcx, const_eval:
112
112
// Set allocation mutability as appropriate. This is used by LLVM to put things into
113
113
// read-only memory, and also by Miri when evaluating other globals that
114
114
// access this one.
115
- if let InternMode :: Static ( mutability) = mode {
116
- // For this, we need to take into account `UnsafeCell`. When `ty` is `None`, we assume
117
- // no interior mutability.
118
- let frozen = ty. map_or ( true , |ty| ty. is_freeze ( * ecx. tcx , ecx. param_env ) ) ;
119
- // For statics, allocation mutability is the combination of place mutability and
120
- // type mutability.
121
- // The entire allocation needs to be mutable if it contains an `UnsafeCell` anywhere.
122
- let immutable = mutability == Mutability :: Not && frozen;
123
- if immutable {
115
+ match mutability {
116
+ Mutability :: Not => {
124
117
alloc. mutability = Mutability :: Not ;
125
- } else {
126
- // Just making sure we are not "upgrading" an immutable allocation to mutable.
118
+ }
119
+ Mutability :: Mut => {
120
+ // This must be already mutable, we won't "un-freeze" allocations ever.
127
121
assert_eq ! ( alloc. mutability, Mutability :: Mut ) ;
128
122
}
129
- } else {
130
- // No matter what, *constants are never mutable*. Mutating them is UB.
131
- // See const_eval::machine::MemoryExtra::can_access_statics for why
132
- // immutability is so important.
133
-
134
- // Validation will ensure that there is no `UnsafeCell` on an immutable allocation.
135
- alloc. mutability = Mutability :: Not ;
136
- } ;
123
+ }
137
124
// link the alloc id to the actual allocation
138
125
leftover_allocations. extend ( alloc. provenance ( ) . ptrs ( ) . iter ( ) . map ( |& ( _, alloc_id) | alloc_id) ) ;
139
126
let alloc = tcx. mk_const_alloc ( alloc) ;
@@ -144,13 +131,8 @@ fn intern_shallow<'rt, 'mir, 'tcx, M: CompileTimeMachine<'mir, 'tcx, const_eval:
144
131
impl < ' rt , ' mir , ' tcx , M : CompileTimeMachine < ' mir , ' tcx , const_eval:: MemoryKind > >
145
132
InternVisitor < ' rt , ' mir , ' tcx , M >
146
133
{
147
- fn intern_shallow (
148
- & mut self ,
149
- alloc_id : AllocId ,
150
- mode : InternMode ,
151
- ty : Option < Ty < ' tcx > > ,
152
- ) -> Option < IsStaticOrFn > {
153
- intern_shallow ( self . ecx , self . leftover_allocations , alloc_id, mode, ty)
134
+ fn intern_shallow ( & mut self , alloc_id : AllocId ) -> Option < IsStaticOrFn > {
135
+ intern_shallow ( self . ecx , self . leftover_allocations , alloc_id, self . intern_mutability )
154
136
}
155
137
}
156
138
@@ -181,7 +163,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: CompileTimeMachine<'mir, 'tcx, const_eval::Memory
181
163
if let Some ( alloc_id) = ptr. provenance {
182
164
// Explicitly choose const mode here, since vtables are immutable, even
183
165
// if the reference of the fat pointer is mutable.
184
- self . intern_shallow ( alloc_id, InternMode :: Const , None ) ;
166
+ self . intern_shallow ( alloc_id) ;
185
167
} else {
186
168
// Validation will error (with a better message) on an invalid vtable pointer.
187
169
// Let validation show the error message, but make sure it *does* error.
@@ -234,7 +216,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: CompileTimeMachine<'mir, 'tcx, const_eval::Memory
234
216
InternMode :: Const
235
217
}
236
218
} ;
237
- match self . intern_shallow ( alloc_id, ref_mode , Some ( referenced_ty ) ) {
219
+ match self . intern_shallow ( alloc_id) {
238
220
// No need to recurse, these are interned already and statics may have
239
221
// cycles, so we don't want to recurse there
240
222
Some ( IsStaticOrFn ) => { }
@@ -332,12 +314,36 @@ pub fn intern_const_alloc_recursive<
332
314
intern_kind : InternKind ,
333
315
ret : & MPlaceTy < ' tcx > ,
334
316
) -> Result < ( ) , ErrorGuaranteed > {
335
- let tcx = ecx. tcx ;
336
317
let base_intern_mode = match intern_kind {
337
318
InternKind :: Static ( mutbl) => InternMode :: Static ( mutbl) ,
338
319
// `Constant` includes array lengths.
339
320
InternKind :: Constant | InternKind :: Promoted => InternMode :: Const ,
340
321
} ;
322
+ // We are interning recursively, and for mutability we are distinguishing the "root" allocation
323
+ // that we are starting in, and all other allocations that we are encountering recursively.
324
+ let ( base_mutability, inner_mutability) = match intern_kind {
325
+ InternKind :: Constant | InternKind :: Promoted => {
326
+ // Completely immutable.
327
+ ( Mutability :: Not , Mutability :: Not )
328
+ }
329
+ InternKind :: Static ( Mutability :: Not ) => {
330
+ (
331
+ // Outermost allocation is mutable if `!Freeze`.
332
+ if ret. layout . ty . is_freeze ( * ecx. tcx , ecx. param_env ) {
333
+ Mutability :: Not
334
+ } else {
335
+ Mutability :: Mut
336
+ } ,
337
+ // Inner allocations are never mutable.
338
+ Mutability :: Not ,
339
+ )
340
+ }
341
+ InternKind :: Static ( Mutability :: Mut ) => {
342
+ // Just make everything mutable. We accept code like
343
+ // `static mut X = &mut 42`, so even inner allocations need to be mutable.
344
+ ( Mutability :: Mut , Mutability :: Mut )
345
+ }
346
+ } ;
341
347
342
348
// Type based interning.
343
349
// `ref_tracking` tracks typed references we have already interned and still need to crawl for
@@ -354,18 +360,22 @@ pub fn intern_const_alloc_recursive<
354
360
// The outermost allocation must exist, because we allocated it with
355
361
// `Memory::allocate`.
356
362
ret. ptr ( ) . provenance . unwrap ( ) ,
357
- base_intern_mode,
358
- Some ( ret. layout . ty ) ,
363
+ base_mutability,
359
364
) ;
360
365
361
366
ref_tracking. track ( ( ret. clone ( ) , base_intern_mode) , || ( ) ) ;
362
367
368
+ // We do a type-based traversal to find more allocations to intern. The interner is currently
369
+ // mid-refactoring; eventually the type-based traversal will be replaced but a simple traversal
370
+ // of all provenance we see in the allocations, but for now we avoid changing rustc error
371
+ // messages or accepting extra programs by keeping the old type-based interner around.
363
372
while let Some ( ( ( mplace, mode) , _) ) = ref_tracking. todo . pop ( ) {
364
373
let res = InternVisitor {
365
374
ref_tracking : & mut ref_tracking,
366
375
ecx,
367
376
mode,
368
377
leftover_allocations,
378
+ intern_mutability : inner_mutability,
369
379
inside_unsafe_cell : false ,
370
380
}
371
381
. visit_value ( & mplace) ;
@@ -394,9 +404,9 @@ pub fn intern_const_alloc_recursive<
394
404
debug ! ( "dead_alloc_map: {:#?}" , ecx. memory. dead_alloc_map) ;
395
405
while let Some ( alloc_id) = todo. pop ( ) {
396
406
if let Some ( ( _, mut alloc) ) = ecx. memory . alloc_map . remove ( & alloc_id) {
397
- // We can't call the `intern_shallow` method here, as its logic is tailored to safe
398
- // references and a `leftover_allocations` set (where we only have a todo-list here).
399
- // So we hand-roll the interning logic here again .
407
+ // This still needs to be interned. We keep the old logic around here to avoid changing
408
+ // rustc behavior; eventually this should all be removed in favor of ignroing all types
409
+ // and interning everything with a simple `intern_shallow` loop .
400
410
match intern_kind {
401
411
// Statics may point to mutable allocations.
402
412
// Even for immutable statics it would be ok to have mutable allocations behind
@@ -410,11 +420,7 @@ pub fn intern_const_alloc_recursive<
410
420
// mutability), mutating through them would be UB.
411
421
// There's no way we can check whether the user is using raw pointers correctly,
412
422
// so all we can do is mark this as immutable here.
413
- InternKind :: Promoted => {
414
- // See const_eval::machine::MemoryExtra::can_access_statics for why
415
- // immutability is so important.
416
- alloc. mutability = Mutability :: Not ;
417
- }
423
+ InternKind :: Promoted => { }
418
424
// If it's a constant, we should not have any "leftovers" as everything
419
425
// is tracked by const-checking.
420
426
// FIXME: downgrade this to a warning? It rejects some legitimate consts,
@@ -425,12 +431,19 @@ pub fn intern_const_alloc_recursive<
425
431
// drop glue, such as the example above.
426
432
InternKind :: Constant => {
427
433
ecx. tcx . sess . emit_err ( UnsupportedUntypedPointer { span : ecx. tcx . span } ) ;
428
- // For better errors later, mark the allocation as immutable.
434
+ }
435
+ }
436
+ match inner_mutability {
437
+ Mutability :: Not => {
429
438
alloc. mutability = Mutability :: Not ;
430
439
}
440
+ Mutability :: Mut => {
441
+ // This must be already mutable, we won't "un-freeze" allocations ever.
442
+ assert_eq ! ( alloc. mutability, Mutability :: Mut ) ;
443
+ }
431
444
}
432
- let alloc = tcx. mk_const_alloc ( alloc) ;
433
- tcx. set_alloc_id_memory ( alloc_id, alloc) ;
445
+ let alloc = ecx . tcx . mk_const_alloc ( alloc) ;
446
+ ecx . tcx . set_alloc_id_memory ( alloc_id, alloc) ;
434
447
for & ( _, alloc_id) in alloc. inner ( ) . provenance ( ) . ptrs ( ) . iter ( ) {
435
448
if leftover_allocations. insert ( alloc_id) {
436
449
todo. push ( alloc_id) ;
0 commit comments