11//! This module is responsible for managing the absolute addresses that allocations are located at,
22//! and for casting between pointers and integers based on those addresses.
33
4+ mod reuse_pool;
5+
46use std:: cell:: RefCell ;
57use std:: cmp:: max;
68use std:: collections:: hash_map:: Entry ;
@@ -9,9 +11,10 @@ use rand::Rng;
911
1012use rustc_data_structures:: fx:: { FxHashMap , FxHashSet } ;
1113use rustc_span:: Span ;
12- use rustc_target:: abi:: { HasDataLayout , Size } ;
14+ use rustc_target:: abi:: { Align , HasDataLayout , Size } ;
1315
1416use crate :: * ;
17+ use reuse_pool:: ReusePool ;
1518
1619#[ derive( Copy , Clone , Debug , PartialEq , Eq ) ]
1720pub enum ProvenanceMode {
@@ -26,7 +29,7 @@ pub enum ProvenanceMode {
2629
2730pub type GlobalState = RefCell < GlobalStateInner > ;
2831
29- #[ derive( Clone , Debug ) ]
32+ #[ derive( Debug ) ]
3033pub struct GlobalStateInner {
3134 /// This is used as a map between the address of each allocation and its `AllocId`. It is always
3235 /// sorted by address. We cannot use a `HashMap` since we can be given an address that is offset
@@ -38,6 +41,8 @@ pub struct GlobalStateInner {
3841 /// they do not have an `AllocExtra`.
3942 /// This is the inverse of `int_to_ptr_map`.
4043 base_addr : FxHashMap < AllocId , u64 > ,
44+ /// A pool of addresses we can reuse for future allocations.
45+ reuse : ReusePool ,
4146 /// Whether an allocation has been exposed or not. This cannot be put
4247 /// into `AllocExtra` for the same reason as `base_addr`.
4348 exposed : FxHashSet < AllocId > ,
@@ -53,6 +58,7 @@ impl VisitProvenance for GlobalStateInner {
5358 let GlobalStateInner {
5459 int_to_ptr_map : _,
5560 base_addr : _,
61+ reuse : _,
5662 exposed : _,
5763 next_base_addr : _,
5864 provenance_mode : _,
@@ -71,6 +77,7 @@ impl GlobalStateInner {
7177 GlobalStateInner {
7278 int_to_ptr_map : Vec :: default ( ) ,
7379 base_addr : FxHashMap :: default ( ) ,
80+ reuse : ReusePool :: new ( ) ,
7481 exposed : FxHashSet :: default ( ) ,
7582 next_base_addr : stack_addr,
7683 provenance_mode : config. provenance_mode ,
@@ -142,6 +149,7 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
142149 Ok ( match global_state. base_addr . entry ( alloc_id) {
143150 Entry :: Occupied ( entry) => * entry. get ( ) ,
144151 Entry :: Vacant ( entry) => {
152+ let mut rng = ecx. machine . rng . borrow_mut ( ) ;
145153 let ( size, align, kind) = ecx. get_alloc_info ( alloc_id) ;
146154 // This is either called immediately after allocation (and then cached), or when
147155 // adjusting `tcx` pointers (which never get freed). So assert that we are looking
@@ -150,44 +158,63 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
150158 // information was removed.
151159 assert ! ( !matches!( kind, AllocKind :: Dead ) ) ;
152160
153- // This allocation does not have a base address yet, pick one.
154- // Leave some space to the previous allocation, to give it some chance to be less aligned.
155- let slack = {
156- let mut rng = ecx. machine . rng . borrow_mut ( ) ;
157- // This means that `(global_state.next_base_addr + slack) % 16` is uniformly distributed.
158- rng. gen_range ( 0 ..16 )
161+ // This allocation does not have a base address yet, pick or reuse one.
162+ let base_addr = if let Some ( reuse_addr) =
163+ global_state. reuse . take_addr ( & mut * rng, size, align)
164+ {
165+ reuse_addr
166+ } else {
167+ // We have to pick a fresh address.
168+ // Leave some space to the previous allocation, to give it some chance to be less aligned.
169+ // We ensure that `(global_state.next_base_addr + slack) % 16` is uniformly distributed.
170+ let slack = rng. gen_range ( 0 ..16 ) ;
171+ // From next_base_addr + slack, round up to adjust for alignment.
172+ let base_addr = global_state
173+ . next_base_addr
174+ . checked_add ( slack)
175+ . ok_or_else ( || err_exhaust ! ( AddressSpaceFull ) ) ?;
176+ let base_addr = align_addr ( base_addr, align. bytes ( ) ) ;
177+
178+ // Remember next base address. If this allocation is zero-sized, leave a gap
179+ // of at least 1 to avoid two allocations having the same base address.
180+ // (The logic in `alloc_id_from_addr` assumes unique addresses, and different
181+ // function/vtable pointers need to be distinguishable!)
182+ global_state. next_base_addr = base_addr
183+ . checked_add ( max ( size. bytes ( ) , 1 ) )
184+ . ok_or_else ( || err_exhaust ! ( AddressSpaceFull ) ) ?;
185+ // Even if `Size` didn't overflow, we might still have filled up the address space.
186+ if global_state. next_base_addr > ecx. target_usize_max ( ) {
187+ throw_exhaust ! ( AddressSpaceFull ) ;
188+ }
189+
190+ base_addr
159191 } ;
160- // From next_base_addr + slack, round up to adjust for alignment.
161- let base_addr = global_state
162- . next_base_addr
163- . checked_add ( slack)
164- . ok_or_else ( || err_exhaust ! ( AddressSpaceFull ) ) ?;
165- let base_addr = align_addr ( base_addr, align. bytes ( ) ) ;
166- entry. insert ( base_addr) ;
167192 trace ! (
168- "Assigning base address {:#x} to allocation {:?} (size: {}, align: {}, slack: {} )" ,
193+ "Assigning base address {:#x} to allocation {:?} (size: {}, align: {})" ,
169194 base_addr,
170195 alloc_id,
171196 size. bytes( ) ,
172197 align. bytes( ) ,
173- slack,
174198 ) ;
175199
176- // Remember next base address. If this allocation is zero-sized, leave a gap
177- // of at least 1 to avoid two allocations having the same base address.
178- // (The logic in `alloc_id_from_addr` assumes unique addresses, and different
179- // function/vtable pointers need to be distinguishable!)
180- global_state. next_base_addr = base_addr
181- . checked_add ( max ( size. bytes ( ) , 1 ) )
182- . ok_or_else ( || err_exhaust ! ( AddressSpaceFull ) ) ?;
183- // Even if `Size` didn't overflow, we might still have filled up the address space.
184- if global_state. next_base_addr > ecx. target_usize_max ( ) {
185- throw_exhaust ! ( AddressSpaceFull ) ;
186- }
187- // Also maintain the opposite mapping in `int_to_ptr_map`.
188- // Given that `next_base_addr` increases in each allocation, pushing the
189- // corresponding tuple keeps `int_to_ptr_map` sorted
190- global_state. int_to_ptr_map . push ( ( base_addr, alloc_id) ) ;
200+ // Store address in cache.
201+ entry. insert ( base_addr) ;
202+
203+ // Also maintain the opposite mapping in `int_to_ptr_map`, ensuring we keep it sorted.
204+ // We have a fast-path for the common case that this address is bigger than all previous ones.
205+ let pos = if global_state
206+ . int_to_ptr_map
207+ . last ( )
208+ . is_some_and ( |( last_addr, _) | * last_addr < base_addr)
209+ {
210+ global_state. int_to_ptr_map . len ( )
211+ } else {
212+ global_state
213+ . int_to_ptr_map
214+ . binary_search_by_key ( & base_addr, |( addr, _) | * addr)
215+ . unwrap_err ( )
216+ } ;
217+ global_state. int_to_ptr_map . insert ( pos, ( base_addr, alloc_id) ) ;
191218
192219 base_addr
193220 }
@@ -302,7 +329,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
302329}
303330
304331impl GlobalStateInner {
305- pub fn free_alloc_id ( & mut self , dead_id : AllocId ) {
332+ pub fn free_alloc_id (
333+ & mut self ,
334+ rng : & mut impl Rng ,
335+ dead_id : AllocId ,
336+ size : Size ,
337+ align : Align ,
338+ ) {
306339 // We can *not* remove this from `base_addr`, since the interpreter design requires that we
307340 // be able to retrieve an AllocId + offset for any memory access *before* we check if the
308341 // access is valid. Specifically, `ptr_get_alloc` is called on each attempt at a memory
@@ -322,6 +355,8 @@ impl GlobalStateInner {
322355 // We can also remove it from `exposed`, since this allocation can anyway not be returned by
323356 // `alloc_id_from_addr` any more.
324357 self . exposed . remove ( & dead_id) ;
358+ // Also remember this address for future reuse.
359+ self . reuse . add_addr ( rng, addr, size, align)
325360 }
326361}
327362
0 commit comments