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
+
1
6
use std:: cell:: RefCell ;
2
7
use std:: cmp:: max;
3
8
use std:: collections:: hash_map:: Entry ;
@@ -6,9 +11,10 @@ use rand::Rng;
6
11
7
12
use rustc_data_structures:: fx:: { FxHashMap , FxHashSet } ;
8
13
use rustc_span:: Span ;
9
- use rustc_target:: abi:: { HasDataLayout , Size } ;
14
+ use rustc_target:: abi:: { Align , HasDataLayout , Size } ;
10
15
11
16
use crate :: * ;
17
+ use reuse_pool:: ReusePool ;
12
18
13
19
#[ derive( Copy , Clone , Debug , PartialEq , Eq ) ]
14
20
pub enum ProvenanceMode {
@@ -23,7 +29,7 @@ pub enum ProvenanceMode {
23
29
24
30
pub type GlobalState = RefCell < GlobalStateInner > ;
25
31
26
- #[ derive( Clone , Debug ) ]
32
+ #[ derive( Debug ) ]
27
33
pub struct GlobalStateInner {
28
34
/// This is used as a map between the address of each allocation and its `AllocId`. It is always
29
35
/// 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 {
35
41
/// they do not have an `AllocExtra`.
36
42
/// This is the inverse of `int_to_ptr_map`.
37
43
base_addr : FxHashMap < AllocId , u64 > ,
44
+ /// A pool of addresses we can reuse for future allocations.
45
+ reuse : ReusePool ,
38
46
/// Whether an allocation has been exposed or not. This cannot be put
39
47
/// into `AllocExtra` for the same reason as `base_addr`.
40
48
exposed : FxHashSet < AllocId > ,
@@ -50,6 +58,7 @@ impl VisitProvenance for GlobalStateInner {
50
58
let GlobalStateInner {
51
59
int_to_ptr_map : _,
52
60
base_addr : _,
61
+ reuse : _,
53
62
exposed : _,
54
63
next_base_addr : _,
55
64
provenance_mode : _,
@@ -68,6 +77,7 @@ impl GlobalStateInner {
68
77
GlobalStateInner {
69
78
int_to_ptr_map : Vec :: default ( ) ,
70
79
base_addr : FxHashMap :: default ( ) ,
80
+ reuse : ReusePool :: new ( ) ,
71
81
exposed : FxHashSet :: default ( ) ,
72
82
next_base_addr : stack_addr,
73
83
provenance_mode : config. provenance_mode ,
@@ -96,7 +106,7 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
96
106
// or `None` if the addr is out of bounds
97
107
fn alloc_id_from_addr ( & self , addr : u64 ) -> Option < AllocId > {
98
108
let ecx = self . eval_context_ref ( ) ;
99
- let global_state = ecx. machine . intptrcast . borrow ( ) ;
109
+ let global_state = ecx. machine . alloc_addresses . borrow ( ) ;
100
110
assert ! ( global_state. provenance_mode != ProvenanceMode :: Strict ) ;
101
111
102
112
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> {
133
143
134
144
fn addr_from_alloc_id ( & self , alloc_id : AllocId ) -> InterpResult < ' tcx , u64 > {
135
145
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 ( ) ;
137
147
let global_state = & mut * global_state;
138
148
139
149
Ok ( match global_state. base_addr . entry ( alloc_id) {
140
150
Entry :: Occupied ( entry) => * entry. get ( ) ,
141
151
Entry :: Vacant ( entry) => {
152
+ let mut rng = ecx. machine . rng . borrow_mut ( ) ;
142
153
let ( size, align, kind) = ecx. get_alloc_info ( alloc_id) ;
143
154
// This is either called immediately after allocation (and then cached), or when
144
155
// 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> {
147
158
// information was removed.
148
159
assert ! ( !matches!( kind, AllocKind :: Dead ) ) ;
149
160
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
156
191
} ;
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) ;
164
192
trace ! (
165
- "Assigning base address {:#x} to allocation {:?} (size: {}, align: {}, slack: {} )" ,
193
+ "Assigning base address {:#x} to allocation {:?} (size: {}, align: {})" ,
166
194
base_addr,
167
195
alloc_id,
168
196
size. bytes( ) ,
169
197
align. bytes( ) ,
170
- slack,
171
198
) ;
172
199
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) ) ;
188
218
189
219
base_addr
190
220
}
@@ -196,7 +226,7 @@ impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir,
196
226
pub trait EvalContextExt < ' mir , ' tcx : ' mir > : crate :: MiriInterpCxExt < ' mir , ' tcx > {
197
227
fn expose_ptr ( & mut self , alloc_id : AllocId , tag : BorTag ) -> InterpResult < ' tcx > {
198
228
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 ( ) ;
200
230
// In strict mode, we don't need this, so we can save some cycles by not tracking it.
201
231
if global_state. provenance_mode == ProvenanceMode :: Strict {
202
232
return Ok ( ( ) ) ;
@@ -207,7 +237,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
207
237
return Ok ( ( ) ) ;
208
238
}
209
239
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 ( ) ;
211
241
global_state. exposed . insert ( alloc_id) ;
212
242
if ecx. machine . borrow_tracker . is_some ( ) {
213
243
ecx. expose_tag ( alloc_id, tag) ?;
@@ -219,7 +249,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
219
249
trace ! ( "Casting {:#x} to a pointer" , addr) ;
220
250
221
251
let ecx = self . eval_context_ref ( ) ;
222
- let global_state = ecx. machine . intptrcast . borrow ( ) ;
252
+ let global_state = ecx. machine . alloc_addresses . borrow ( ) ;
223
253
224
254
// Potentially emit a warning.
225
255
match global_state. provenance_mode {
@@ -299,7 +329,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
299
329
}
300
330
301
331
impl 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
+ ) {
303
339
// We can *not* remove this from `base_addr`, since the interpreter design requires that we
304
340
// be able to retrieve an AllocId + offset for any memory access *before* we check if the
305
341
// access is valid. Specifically, `ptr_get_alloc` is called on each attempt at a memory
@@ -319,6 +355,8 @@ impl GlobalStateInner {
319
355
// We can also remove it from `exposed`, since this allocation can anyway not be returned by
320
356
// `alloc_id_from_addr` any more.
321
357
self . exposed . remove ( & dead_id) ;
358
+ // Also remember this address for future reuse.
359
+ self . reuse . add_addr ( rng, addr, size, align)
322
360
}
323
361
}
324
362
0 commit comments