@@ -10,11 +10,13 @@ mod value;
10
10
11
11
use std:: io:: { Read , Write } ;
12
12
use std:: num:: NonZero ;
13
+ use std:: sync:: atomic:: AtomicU64 ;
13
14
use std:: { fmt, io} ;
14
15
15
16
use rustc_abi:: { AddressSpace , Align , Endian , HasDataLayout , Size } ;
16
17
use rustc_ast:: { LitKind , Mutability } ;
17
18
use rustc_data_structures:: fx:: FxHashMap ;
19
+ use rustc_data_structures:: sharded:: ShardedHashMap ;
18
20
use rustc_data_structures:: sync:: Lock ;
19
21
use rustc_hir:: def:: DefKind ;
20
22
use rustc_hir:: def_id:: { DefId , LocalDefId } ;
@@ -389,35 +391,39 @@ pub const CTFE_ALLOC_SALT: usize = 0;
389
391
390
392
pub ( crate ) struct AllocMap < ' tcx > {
391
393
/// Maps `AllocId`s to their corresponding allocations.
392
- alloc_map : FxHashMap < AllocId , GlobalAlloc < ' tcx > > ,
394
+ // Note that this map on rustc workloads seems to be rather dense, but in miri workloads should
395
+ // be pretty sparse. In #136105 we considered replacing it with a (dense) Vec-based map, but
396
+ // since there are workloads where it can be sparse we decided to go with sharding for now. At
397
+ // least up to 32 cores the one workload tested didn't exhibit much difference between the two.
398
+ //
399
+ // Should be locked *after* locking dedup if locking both to avoid deadlocks.
400
+ to_alloc : ShardedHashMap < AllocId , GlobalAlloc < ' tcx > > ,
393
401
394
402
/// Used to deduplicate global allocations: functions, vtables, string literals, ...
395
403
///
396
404
/// The `usize` is a "salt" used by Miri to make deduplication imperfect, thus better emulating
397
405
/// the actual guarantees.
398
- dedup : FxHashMap < ( GlobalAlloc < ' tcx > , usize ) , AllocId > ,
406
+ dedup : Lock < FxHashMap < ( GlobalAlloc < ' tcx > , usize ) , AllocId > > ,
399
407
400
408
/// The `AllocId` to assign to the next requested ID.
401
409
/// Always incremented; never gets smaller.
402
- next_id : AllocId ,
410
+ next_id : AtomicU64 ,
403
411
}
404
412
405
413
impl < ' tcx > AllocMap < ' tcx > {
406
414
pub ( crate ) fn new ( ) -> Self {
407
415
AllocMap {
408
- alloc_map : Default :: default ( ) ,
416
+ to_alloc : Default :: default ( ) ,
409
417
dedup : Default :: default ( ) ,
410
- next_id : AllocId ( NonZero :: new ( 1 ) . unwrap ( ) ) ,
418
+ next_id : AtomicU64 :: new ( 1 ) ,
411
419
}
412
420
}
413
- fn reserve ( & mut self ) -> AllocId {
414
- let next = self . next_id ;
415
- self . next_id . 0 = self . next_id . 0 . checked_add ( 1 ) . expect (
416
- "You overflowed a u64 by incrementing by 1... \
417
- You've just earned yourself a free drink if we ever meet. \
418
- Seriously, how did you do that?!",
419
- ) ;
420
- next
421
+ fn reserve ( & self ) -> AllocId {
422
+ // Technically there is a window here where we overflow and then another thread
423
+ // increments `next_id` *again* and uses it before we panic and tear down the entire session.
424
+ // We consider this fine since such overflows cannot realistically occur.
425
+ let next_id = self . next_id . fetch_add ( 1 , std:: sync:: atomic:: Ordering :: Relaxed ) ;
426
+ AllocId ( NonZero :: new ( next_id) . unwrap ( ) )
421
427
}
422
428
}
423
429
@@ -428,26 +434,34 @@ impl<'tcx> TyCtxt<'tcx> {
428
434
/// Make sure to call `set_alloc_id_memory` or `set_alloc_id_same_memory` before returning such
429
435
/// an `AllocId` from a query.
430
436
pub fn reserve_alloc_id ( self ) -> AllocId {
431
- self . alloc_map . lock ( ) . reserve ( )
437
+ self . alloc_map . reserve ( )
432
438
}
433
439
434
440
/// Reserves a new ID *if* this allocation has not been dedup-reserved before.
435
441
/// Should not be used for mutable memory.
436
442
fn reserve_and_set_dedup ( self , alloc : GlobalAlloc < ' tcx > , salt : usize ) -> AllocId {
437
- let mut alloc_map = self . alloc_map . lock ( ) ;
438
443
if let GlobalAlloc :: Memory ( mem) = alloc {
439
444
if mem. inner ( ) . mutability . is_mut ( ) {
440
445
bug ! ( "trying to dedup-reserve mutable memory" ) ;
441
446
}
442
447
}
443
448
let alloc_salt = ( alloc, salt) ;
444
- if let Some ( & alloc_id) = alloc_map. dedup . get ( & alloc_salt) {
449
+ // Locking this *before* `to_alloc` also to ensure correct lock order.
450
+ let mut dedup = self . alloc_map . dedup . lock ( ) ;
451
+ if let Some ( & alloc_id) = dedup. get ( & alloc_salt) {
445
452
return alloc_id;
446
453
}
447
- let id = alloc_map. reserve ( ) ;
454
+ let id = self . alloc_map . reserve ( ) ;
448
455
debug ! ( "creating alloc {:?} with id {id:?}" , alloc_salt. 0 ) ;
449
- alloc_map. alloc_map . insert ( id, alloc_salt. 0 . clone ( ) ) ;
450
- alloc_map. dedup . insert ( alloc_salt, id) ;
456
+ let had_previous = self
457
+ . alloc_map
458
+ . to_alloc
459
+ . lock_shard_by_value ( & id)
460
+ . insert ( id, alloc_salt. 0 . clone ( ) )
461
+ . is_some ( ) ;
462
+ // We just reserved, so should always be unique.
463
+ assert ! ( !had_previous) ;
464
+ dedup. insert ( alloc_salt, id) ;
451
465
id
452
466
}
453
467
@@ -497,7 +511,7 @@ impl<'tcx> TyCtxt<'tcx> {
497
511
/// local dangling pointers and allocations in constants/statics.
498
512
#[ inline]
499
513
pub fn try_get_global_alloc ( self , id : AllocId ) -> Option < GlobalAlloc < ' tcx > > {
500
- self . alloc_map . lock ( ) . alloc_map . get ( & id) . cloned ( )
514
+ self . alloc_map . to_alloc . lock_shard_by_value ( & id ) . get ( & id) . cloned ( )
501
515
}
502
516
503
517
#[ inline]
@@ -516,16 +530,21 @@ impl<'tcx> TyCtxt<'tcx> {
516
530
/// Freezes an `AllocId` created with `reserve` by pointing it at an `Allocation`. Trying to
517
531
/// call this function twice, even with the same `Allocation` will ICE the compiler.
518
532
pub fn set_alloc_id_memory ( self , id : AllocId , mem : ConstAllocation < ' tcx > ) {
519
- if let Some ( old) = self . alloc_map . lock ( ) . alloc_map . insert ( id, GlobalAlloc :: Memory ( mem) ) {
533
+ if let Some ( old) =
534
+ self . alloc_map . to_alloc . lock_shard_by_value ( & id) . insert ( id, GlobalAlloc :: Memory ( mem) )
535
+ {
520
536
bug ! ( "tried to set allocation ID {id:?}, but it was already existing as {old:#?}" ) ;
521
537
}
522
538
}
523
539
524
540
/// Freezes an `AllocId` created with `reserve` by pointing it at a static item. Trying to
525
541
/// call this function twice, even with the same `DefId` will ICE the compiler.
526
542
pub fn set_nested_alloc_id_static ( self , id : AllocId , def_id : LocalDefId ) {
527
- if let Some ( old) =
528
- self . alloc_map . lock ( ) . alloc_map . insert ( id, GlobalAlloc :: Static ( def_id. to_def_id ( ) ) )
543
+ if let Some ( old) = self
544
+ . alloc_map
545
+ . to_alloc
546
+ . lock_shard_by_value ( & id)
547
+ . insert ( id, GlobalAlloc :: Static ( def_id. to_def_id ( ) ) )
529
548
{
530
549
bug ! ( "tried to set allocation ID {id:?}, but it was already existing as {old:#?}" ) ;
531
550
}
0 commit comments