1+ //! This module is responsible for managing the absolute addresses that allocations are located at,
2+ //! and for casting between pointers and integers based on those addresses.
3+
4+ mod reuse_pool;
5+
16use std:: cell:: RefCell ;
27use std:: cmp:: max;
38use std:: collections:: hash_map:: Entry ;
@@ -6,9 +11,10 @@ use rand::Rng;
611
712use rustc_data_structures:: fx:: { FxHashMap , FxHashSet } ;
813use rustc_span:: Span ;
9- use rustc_target:: abi:: { HasDataLayout , Size } ;
14+ use rustc_target:: abi:: { Align , HasDataLayout , Size } ;
1015
1116use crate :: * ;
17+ use reuse_pool:: ReusePool ;
1218
1319#[ derive( Copy , Clone , Debug , PartialEq , Eq ) ]
1420pub enum ProvenanceMode {
@@ -23,7 +29,7 @@ pub enum ProvenanceMode {
2329
2430pub type GlobalState = RefCell < GlobalStateInner > ;
2531
26- #[ derive( Clone , Debug ) ]
32+ #[ derive( Debug ) ]
2733pub struct GlobalStateInner {
2834 /// This is used as a map between the address of each allocation and its `AllocId`. It is always
2935 /// sorted by address. We cannot use a `HashMap` since we can be given an address that is offset
@@ -35,6 +41,8 @@ pub struct GlobalStateInner {
3541 /// they do not have an `AllocExtra`.
3642 /// This is the inverse of `int_to_ptr_map`.
3743 base_addr : FxHashMap < AllocId , u64 > ,
44+ /// A pool of addresses we can reuse for future allocations.
45+ reuse : ReusePool ,
3846 /// Whether an allocation has been exposed or not. This cannot be put
3947 /// into `AllocExtra` for the same reason as `base_addr`.
4048 exposed : FxHashSet < AllocId > ,
@@ -50,6 +58,7 @@ impl VisitProvenance for GlobalStateInner {
5058 let GlobalStateInner {
5159 int_to_ptr_map : _,
5260 base_addr : _,
61+ reuse : _,
5362 exposed : _,
5463 next_base_addr : _,
5564 provenance_mode : _,
@@ -68,6 +77,7 @@ impl GlobalStateInner {
6877 GlobalStateInner {
6978 int_to_ptr_map : Vec :: default ( ) ,
7079 base_addr : FxHashMap :: default ( ) ,
80+ reuse : ReusePool :: new ( ) ,
7181 exposed : FxHashSet :: default ( ) ,
7282 next_base_addr : stack_addr,
7383 provenance_mode : config. provenance_mode ,
@@ -96,7 +106,7 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
96106 // or `None` if the addr is out of bounds
97107 fn alloc_id_from_addr ( & self , addr : u64 ) -> Option < AllocId > {
98108 let ecx = self . eval_context_ref ( ) ;
99- let global_state = ecx. machine . intptrcast . borrow ( ) ;
109+ let global_state = ecx. machine . alloc_addresses . borrow ( ) ;
100110 assert ! ( global_state. provenance_mode != ProvenanceMode :: Strict ) ;
101111
102112 let pos = global_state. int_to_ptr_map . binary_search_by_key ( & addr, |( addr, _) | * addr) ;
@@ -133,12 +143,13 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
133143
134144 fn addr_from_alloc_id ( & self , alloc_id : AllocId ) -> InterpResult < ' tcx , u64 > {
135145 let ecx = self . eval_context_ref ( ) ;
136- let mut global_state = ecx. machine . intptrcast . borrow_mut ( ) ;
146+ let mut global_state = ecx. machine . alloc_addresses . borrow_mut ( ) ;
137147 let global_state = & mut * global_state;
138148
139149 Ok ( match global_state. base_addr . entry ( alloc_id) {
140150 Entry :: Occupied ( entry) => * entry. get ( ) ,
141151 Entry :: Vacant ( entry) => {
152+ let mut rng = ecx. machine . rng . borrow_mut ( ) ;
142153 let ( size, align, kind) = ecx. get_alloc_info ( alloc_id) ;
143154 // This is either called immediately after allocation (and then cached), or when
144155 // adjusting `tcx` pointers (which never get freed). So assert that we are looking
@@ -147,44 +158,63 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
147158 // information was removed.
148159 assert ! ( !matches!( kind, AllocKind :: Dead ) ) ;
149160
150- // This allocation does not have a base address yet, pick one.
151- // Leave some space to the previous allocation, to give it some chance to be less aligned.
152- let slack = {
153- let mut rng = ecx. machine . rng . borrow_mut ( ) ;
154- // This means that `(global_state.next_base_addr + slack) % 16` is uniformly distributed.
155- 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
156191 } ;
157- // From next_base_addr + slack, round up to adjust for alignment.
158- let base_addr = global_state
159- . next_base_addr
160- . checked_add ( slack)
161- . ok_or_else ( || err_exhaust ! ( AddressSpaceFull ) ) ?;
162- let base_addr = align_addr ( base_addr, align. bytes ( ) ) ;
163- entry. insert ( base_addr) ;
164192 trace ! (
165- "Assigning base address {:#x} to allocation {:?} (size: {}, align: {}, slack: {} )" ,
193+ "Assigning base address {:#x} to allocation {:?} (size: {}, align: {})" ,
166194 base_addr,
167195 alloc_id,
168196 size. bytes( ) ,
169197 align. bytes( ) ,
170- slack,
171198 ) ;
172199
173- // Remember next base address. If this allocation is zero-sized, leave a gap
174- // of at least 1 to avoid two allocations having the same base address.
175- // (The logic in `alloc_id_from_addr` assumes unique addresses, and different
176- // function/vtable pointers need to be distinguishable!)
177- global_state. next_base_addr = base_addr
178- . checked_add ( max ( size. bytes ( ) , 1 ) )
179- . ok_or_else ( || err_exhaust ! ( AddressSpaceFull ) ) ?;
180- // Even if `Size` didn't overflow, we might still have filled up the address space.
181- if global_state. next_base_addr > ecx. target_usize_max ( ) {
182- throw_exhaust ! ( AddressSpaceFull ) ;
183- }
184- // Also maintain the opposite mapping in `int_to_ptr_map`.
185- // Given that `next_base_addr` increases in each allocation, pushing the
186- // corresponding tuple keeps `int_to_ptr_map` sorted
187- 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) ) ;
188218
189219 base_addr
190220 }
@@ -196,7 +226,7 @@ impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir,
196226pub trait EvalContextExt < ' mir , ' tcx : ' mir > : crate :: MiriInterpCxExt < ' mir , ' tcx > {
197227 fn expose_ptr ( & mut self , alloc_id : AllocId , tag : BorTag ) -> InterpResult < ' tcx > {
198228 let ecx = self . eval_context_mut ( ) ;
199- let global_state = ecx. machine . intptrcast . get_mut ( ) ;
229+ let global_state = ecx. machine . alloc_addresses . get_mut ( ) ;
200230 // In strict mode, we don't need this, so we can save some cycles by not tracking it.
201231 if global_state. provenance_mode == ProvenanceMode :: Strict {
202232 return Ok ( ( ) ) ;
@@ -207,7 +237,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
207237 return Ok ( ( ) ) ;
208238 }
209239 trace ! ( "Exposing allocation id {alloc_id:?}" ) ;
210- let global_state = ecx. machine . intptrcast . get_mut ( ) ;
240+ let global_state = ecx. machine . alloc_addresses . get_mut ( ) ;
211241 global_state. exposed . insert ( alloc_id) ;
212242 if ecx. machine . borrow_tracker . is_some ( ) {
213243 ecx. expose_tag ( alloc_id, tag) ?;
@@ -219,7 +249,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
219249 trace ! ( "Casting {:#x} to a pointer" , addr) ;
220250
221251 let ecx = self . eval_context_ref ( ) ;
222- let global_state = ecx. machine . intptrcast . borrow ( ) ;
252+ let global_state = ecx. machine . alloc_addresses . borrow ( ) ;
223253
224254 // Potentially emit a warning.
225255 match global_state. provenance_mode {
@@ -299,7 +329,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
299329}
300330
301331impl GlobalStateInner {
302- 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+ ) {
303339 // We can *not* remove this from `base_addr`, since the interpreter design requires that we
304340 // be able to retrieve an AllocId + offset for any memory access *before* we check if the
305341 // access is valid. Specifically, `ptr_get_alloc` is called on each attempt at a memory
@@ -319,6 +355,8 @@ impl GlobalStateInner {
319355 // We can also remove it from `exposed`, since this allocation can anyway not be returned by
320356 // `alloc_id_from_addr` any more.
321357 self . exposed . remove ( & dead_id) ;
358+ // Also remember this address for future reuse.
359+ self . reuse . add_addr ( rng, addr, size, align)
322360 }
323361}
324362
0 commit comments