@@ -5,7 +5,6 @@ mod reuse_pool;
5
5
6
6
use std:: cell:: RefCell ;
7
7
use std:: cmp:: max;
8
- use std:: collections:: hash_map:: Entry ;
9
8
10
9
use rand:: Rng ;
11
10
@@ -42,6 +41,11 @@ pub struct GlobalStateInner {
42
41
/// they do not have an `AllocExtra`.
43
42
/// This is the inverse of `int_to_ptr_map`.
44
43
base_addr : FxHashMap < AllocId , u64 > ,
44
+ /// Temporarily store prepared memory space for global allocations the first time their memory
45
+ /// address is required. This is used to ensure that the memory is allocated before Miri assigns
46
+ /// it an internal address, which is important for matching the internal address to the machine
47
+ /// address so FFI can read from pointers.
48
+ prepared_alloc_bytes : FxHashMap < AllocId , MiriAllocBytes > ,
45
49
/// A pool of addresses we can reuse for future allocations.
46
50
reuse : ReusePool ,
47
51
/// Whether an allocation has been exposed or not. This cannot be put
@@ -59,6 +63,7 @@ impl VisitProvenance for GlobalStateInner {
59
63
let GlobalStateInner {
60
64
int_to_ptr_map : _,
61
65
base_addr : _,
66
+ prepared_alloc_bytes : _,
62
67
reuse : _,
63
68
exposed : _,
64
69
next_base_addr : _,
@@ -78,6 +83,7 @@ impl GlobalStateInner {
78
83
GlobalStateInner {
79
84
int_to_ptr_map : Vec :: default ( ) ,
80
85
base_addr : FxHashMap :: default ( ) ,
86
+ prepared_alloc_bytes : FxHashMap :: default ( ) ,
81
87
reuse : ReusePool :: new ( config) ,
82
88
exposed : FxHashSet :: default ( ) ,
83
89
next_base_addr : stack_addr,
@@ -144,6 +150,95 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
144
150
}
145
151
}
146
152
153
+ fn addr_from_alloc_id_uncached (
154
+ & self ,
155
+ global_state : & mut GlobalStateInner ,
156
+ alloc_id : AllocId ,
157
+ memory_kind : MemoryKind ,
158
+ ) -> InterpResult < ' tcx , u64 > {
159
+ let ecx = self . eval_context_ref ( ) ;
160
+ let mut rng = ecx. machine . rng . borrow_mut ( ) ;
161
+ let ( size, align, kind) = ecx. get_alloc_info ( alloc_id) ;
162
+ // This is either called immediately after allocation (and then cached), or when
163
+ // adjusting `tcx` pointers (which never get freed). So assert that we are looking
164
+ // at a live allocation. This also ensures that we never re-assign an address to an
165
+ // allocation that previously had an address, but then was freed and the address
166
+ // information was removed.
167
+ assert ! ( !matches!( kind, AllocKind :: Dead ) ) ;
168
+
169
+ // This allocation does not have a base address yet, pick or reuse one.
170
+ if ecx. machine . native_lib . is_some ( ) {
171
+ // In native lib mode, we use the "real" address of the bytes for this allocation.
172
+ // This ensures the interpreted program and native code have the same view of memory.
173
+ let base_ptr = match kind {
174
+ AllocKind :: LiveData => {
175
+ if ecx. tcx . try_get_global_alloc ( alloc_id) . is_some ( ) {
176
+ // For new global allocations, we always pre-allocate the memory to be able use the machine address directly.
177
+ let prepared_bytes = MiriAllocBytes :: zeroed ( size, align)
178
+ . unwrap_or_else ( || {
179
+ panic ! ( "Miri ran out of memory: cannot create allocation of {size:?} bytes" )
180
+ } ) ;
181
+ let ptr = prepared_bytes. as_ptr ( ) ;
182
+ // Store prepared allocation space to be picked up for use later.
183
+ global_state
184
+ . prepared_alloc_bytes
185
+ . try_insert ( alloc_id, prepared_bytes)
186
+ . unwrap ( ) ;
187
+ ptr
188
+ } else {
189
+ ecx. get_alloc_bytes_unchecked_raw ( alloc_id) ?
190
+ }
191
+ }
192
+ AllocKind :: Function | AllocKind :: VTable => {
193
+ // Allocate some dummy memory to get a unique address for this function/vtable.
194
+ let alloc_bytes =
195
+ MiriAllocBytes :: from_bytes ( & [ 0u8 ; 1 ] , Align :: from_bytes ( 1 ) . unwrap ( ) ) ;
196
+ let ptr = alloc_bytes. as_ptr ( ) ;
197
+ // Leak the underlying memory to ensure it remains unique.
198
+ std:: mem:: forget ( alloc_bytes) ;
199
+ ptr
200
+ }
201
+ AllocKind :: Dead => unreachable ! ( ) ,
202
+ } ;
203
+ // Ensure this pointer's provenance is exposed, so that it can be used by FFI code.
204
+ return Ok ( base_ptr. expose_provenance ( ) . try_into ( ) . unwrap ( ) ) ;
205
+ }
206
+ // We are not in native lib mode, so we control the addresses ourselves.
207
+ if let Some ( ( reuse_addr, clock) ) =
208
+ global_state. reuse . take_addr ( & mut * rng, size, align, memory_kind, ecx. active_thread ( ) )
209
+ {
210
+ if let Some ( clock) = clock {
211
+ ecx. acquire_clock ( & clock) ;
212
+ }
213
+ Ok ( reuse_addr)
214
+ } else {
215
+ // We have to pick a fresh address.
216
+ // Leave some space to the previous allocation, to give it some chance to be less aligned.
217
+ // We ensure that `(global_state.next_base_addr + slack) % 16` is uniformly distributed.
218
+ let slack = rng. gen_range ( 0 ..16 ) ;
219
+ // From next_base_addr + slack, round up to adjust for alignment.
220
+ let base_addr = global_state
221
+ . next_base_addr
222
+ . checked_add ( slack)
223
+ . ok_or_else ( || err_exhaust ! ( AddressSpaceFull ) ) ?;
224
+ let base_addr = align_addr ( base_addr, align. bytes ( ) ) ;
225
+
226
+ // Remember next base address. If this allocation is zero-sized, leave a gap of at
227
+ // least 1 to avoid two allocations having the same base address. (The logic in
228
+ // `alloc_id_from_addr` assumes unique addresses, and different function/vtable pointers
229
+ // need to be distinguishable!)
230
+ global_state. next_base_addr = base_addr
231
+ . checked_add ( max ( size. bytes ( ) , 1 ) )
232
+ . ok_or_else ( || err_exhaust ! ( AddressSpaceFull ) ) ?;
233
+ // Even if `Size` didn't overflow, we might still have filled up the address space.
234
+ if global_state. next_base_addr > ecx. target_usize_max ( ) {
235
+ throw_exhaust ! ( AddressSpaceFull ) ;
236
+ }
237
+
238
+ Ok ( base_addr)
239
+ }
240
+ }
241
+
147
242
fn addr_from_alloc_id (
148
243
& self ,
149
244
alloc_id : AllocId ,
@@ -153,66 +248,16 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
153
248
let mut global_state = ecx. machine . alloc_addresses . borrow_mut ( ) ;
154
249
let global_state = & mut * global_state;
155
250
156
- Ok ( match global_state. base_addr . entry ( alloc_id) {
157
- Entry :: Occupied ( entry) => * entry. get ( ) ,
158
- Entry :: Vacant ( entry) => {
159
- let mut rng = ecx. machine . rng . borrow_mut ( ) ;
160
- let ( size, align, kind) = ecx. get_alloc_info ( alloc_id) ;
161
- // This is either called immediately after allocation (and then cached), or when
162
- // adjusting `tcx` pointers (which never get freed). So assert that we are looking
163
- // at a live allocation. This also ensures that we never re-assign an address to an
164
- // allocation that previously had an address, but then was freed and the address
165
- // information was removed.
166
- assert ! ( !matches!( kind, AllocKind :: Dead ) ) ;
167
-
168
- // This allocation does not have a base address yet, pick or reuse one.
169
- let base_addr = if let Some ( ( reuse_addr, clock) ) = global_state. reuse . take_addr (
170
- & mut * rng,
171
- size,
172
- align,
173
- memory_kind,
174
- ecx. active_thread ( ) ,
175
- ) {
176
- if let Some ( clock) = clock {
177
- ecx. acquire_clock ( & clock) ;
178
- }
179
- reuse_addr
180
- } else {
181
- // We have to pick a fresh address.
182
- // Leave some space to the previous allocation, to give it some chance to be less aligned.
183
- // We ensure that `(global_state.next_base_addr + slack) % 16` is uniformly distributed.
184
- let slack = rng. gen_range ( 0 ..16 ) ;
185
- // From next_base_addr + slack, round up to adjust for alignment.
186
- let base_addr = global_state
187
- . next_base_addr
188
- . checked_add ( slack)
189
- . ok_or_else ( || err_exhaust ! ( AddressSpaceFull ) ) ?;
190
- let base_addr = align_addr ( base_addr, align. bytes ( ) ) ;
191
-
192
- // Remember next base address. If this allocation is zero-sized, leave a gap
193
- // of at least 1 to avoid two allocations having the same base address.
194
- // (The logic in `alloc_id_from_addr` assumes unique addresses, and different
195
- // function/vtable pointers need to be distinguishable!)
196
- global_state. next_base_addr = base_addr
197
- . checked_add ( max ( size. bytes ( ) , 1 ) )
198
- . ok_or_else ( || err_exhaust ! ( AddressSpaceFull ) ) ?;
199
- // Even if `Size` didn't overflow, we might still have filled up the address space.
200
- if global_state. next_base_addr > ecx. target_usize_max ( ) {
201
- throw_exhaust ! ( AddressSpaceFull ) ;
202
- }
203
-
204
- base_addr
205
- } ;
206
- trace ! (
207
- "Assigning base address {:#x} to allocation {:?} (size: {}, align: {})" ,
208
- base_addr,
209
- alloc_id,
210
- size. bytes( ) ,
211
- align. bytes( ) ,
212
- ) ;
251
+ match global_state. base_addr . get ( & alloc_id) {
252
+ Some ( & addr) => Ok ( addr) ,
253
+ None => {
254
+ // First time we're looking for the absolute address of this allocation.
255
+ let base_addr =
256
+ self . addr_from_alloc_id_uncached ( global_state, alloc_id, memory_kind) ?;
257
+ trace ! ( "Assigning base address {:#x} to allocation {:?}" , base_addr, alloc_id) ;
213
258
214
259
// Store address in cache.
215
- entry . insert ( base_addr) ;
260
+ global_state . base_addr . try_insert ( alloc_id , base_addr) . unwrap ( ) ;
216
261
217
262
// Also maintain the opposite mapping in `int_to_ptr_map`, ensuring we keep it sorted.
218
263
// We have a fast-path for the common case that this address is bigger than all previous ones.
@@ -230,9 +275,9 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
230
275
} ;
231
276
global_state. int_to_ptr_map . insert ( pos, ( base_addr, alloc_id) ) ;
232
277
233
- base_addr
278
+ Ok ( base_addr)
234
279
}
235
- } )
280
+ }
236
281
}
237
282
}
238
283
@@ -318,6 +363,40 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
318
363
Ok ( base_ptr. wrapping_offset ( offset, ecx) )
319
364
}
320
365
366
+ // This returns some prepared `MiriAllocBytes`, either because `addr_from_alloc_id` reserved
367
+ // memory space in the past, or by doing the pre-allocation right upon being called.
368
+ fn get_global_alloc_bytes (
369
+ & self ,
370
+ id : AllocId ,
371
+ kind : MemoryKind ,
372
+ bytes : & [ u8 ] ,
373
+ align : Align ,
374
+ ) -> InterpResult < ' tcx , MiriAllocBytes > {
375
+ let ecx = self . eval_context_ref ( ) ;
376
+ if ecx. machine . native_lib . is_some ( ) {
377
+ // In native lib mode, MiriAllocBytes for global allocations are handled via `prepared_alloc_bytes`.
378
+ // This additional call ensures that some `MiriAllocBytes` are always prepared, just in case
379
+ // this function gets called before the first time `addr_from_alloc_id` gets called.
380
+ ecx. addr_from_alloc_id ( id, kind) ?;
381
+ // The memory we need here will have already been allocated during an earlier call to
382
+ // `addr_from_alloc_id` for this allocation. So don't create a new `MiriAllocBytes` here, instead
383
+ // fetch the previously prepared bytes from `prepared_alloc_bytes`.
384
+ let mut global_state = ecx. machine . alloc_addresses . borrow_mut ( ) ;
385
+ let mut prepared_alloc_bytes = global_state
386
+ . prepared_alloc_bytes
387
+ . remove ( & id)
388
+ . unwrap_or_else ( || panic ! ( "alloc bytes for {id:?} have not been prepared" ) ) ;
389
+ // Sanity-check that the prepared allocation has the right size and alignment.
390
+ assert ! ( prepared_alloc_bytes. as_ptr( ) . is_aligned_to( align. bytes_usize( ) ) ) ;
391
+ assert_eq ! ( prepared_alloc_bytes. len( ) , bytes. len( ) ) ;
392
+ // Copy allocation contents into prepared memory.
393
+ prepared_alloc_bytes. copy_from_slice ( bytes) ;
394
+ Ok ( prepared_alloc_bytes)
395
+ } else {
396
+ Ok ( MiriAllocBytes :: from_bytes ( std:: borrow:: Cow :: Borrowed ( bytes) , align) )
397
+ }
398
+ }
399
+
321
400
/// When a pointer is used for a memory access, this computes where in which allocation the
322
401
/// access is going.
323
402
fn ptr_get_alloc (
0 commit comments