@@ -5,7 +5,6 @@ mod reuse_pool;
55
66use std:: cell:: RefCell ;
77use std:: cmp:: max;
8- use std:: collections:: hash_map:: Entry ;
98
109use rand:: Rng ;
1110
@@ -151,6 +150,95 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
151150 }
152151 }
153152
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+
154242 fn addr_from_alloc_id (
155243 & self ,
156244 alloc_id : AllocId ,
@@ -160,98 +248,16 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
160248 let mut global_state = ecx. machine . alloc_addresses . borrow_mut ( ) ;
161249 let global_state = & mut * global_state;
162250
163- Ok ( match global_state. base_addr . entry ( alloc_id) {
164- Entry :: Occupied ( entry) => * entry. get ( ) ,
165- Entry :: Vacant ( entry) => {
166- let mut rng = ecx. machine . rng . borrow_mut ( ) ;
167- let ( size, align, kind) = ecx. get_alloc_info ( alloc_id) ;
168- // This is either called immediately after allocation (and then cached), or when
169- // adjusting `tcx` pointers (which never get freed). So assert that we are looking
170- // at a live allocation. This also ensures that we never re-assign an address to an
171- // allocation that previously had an address, but then was freed and the address
172- // information was removed.
173- assert ! ( !matches!( kind, AllocKind :: Dead ) ) ;
174-
175- // This allocation does not have a base address yet, pick or reuse one.
176- let base_addr = if ecx. machine . native_lib . is_some ( ) {
177- // In native lib mode, we use the "real" address of the bytes for this allocation.
178- // This ensures the interpreted program and native code have the same view of memory.
179- match kind {
180- AllocKind :: LiveData => {
181- let ptr = if ecx. tcx . try_get_global_alloc ( alloc_id) . is_some ( ) {
182- // For new global allocations, we always pre-allocate the memory to be able use the machine address directly.
183- let prepared_bytes = MiriAllocBytes :: zeroed ( size, align)
184- . unwrap_or_else ( || {
185- panic ! ( "Miri ran out of memory: cannot create allocation of {size:?} bytes" )
186- } ) ;
187- let ptr = prepared_bytes. as_ptr ( ) ;
188- // Store prepared allocation space to be picked up for use later.
189- global_state. prepared_alloc_bytes . try_insert ( alloc_id, prepared_bytes) . unwrap ( ) ;
190- ptr
191- } else {
192- ecx. get_alloc_bytes_unchecked_raw ( alloc_id) ?
193- } ;
194- // Ensure this pointer's provenance is exposed, so that it can be used by FFI code.
195- ptr. expose_provenance ( ) . try_into ( ) . unwrap ( )
196- }
197- AllocKind :: Function | AllocKind :: VTable => {
198- // Allocate some dummy memory to get a unique address for this function/vtable.
199- let alloc_bytes = MiriAllocBytes :: from_bytes ( & [ 0u8 ; 1 ] , Align :: from_bytes ( 1 ) . unwrap ( ) ) ;
200- // We don't need to expose these bytes as nobody is allowed to access them.
201- let addr = alloc_bytes. as_ptr ( ) . addr ( ) . try_into ( ) . unwrap ( ) ;
202- // Leak the underlying memory to ensure it remains unique.
203- std:: mem:: forget ( alloc_bytes) ;
204- addr
205- }
206- AllocKind :: Dead => unreachable ! ( )
207- }
208- } else if let Some ( ( reuse_addr, clock) ) = global_state. reuse . take_addr (
209- & mut * rng,
210- size,
211- align,
212- memory_kind,
213- ecx. active_thread ( ) ,
214- ) {
215- if let Some ( clock) = clock {
216- ecx. acquire_clock ( & clock) ;
217- }
218- reuse_addr
219- } else {
220- // We have to pick a fresh address.
221- // Leave some space to the previous allocation, to give it some chance to be less aligned.
222- // We ensure that `(global_state.next_base_addr + slack) % 16` is uniformly distributed.
223- let slack = rng. gen_range ( 0 ..16 ) ;
224- // From next_base_addr + slack, round up to adjust for alignment.
225- let base_addr = global_state
226- . next_base_addr
227- . checked_add ( slack)
228- . ok_or_else ( || err_exhaust ! ( AddressSpaceFull ) ) ?;
229- let base_addr = align_addr ( base_addr, align. bytes ( ) ) ;
230-
231- // Remember next base address. If this allocation is zero-sized, leave a gap
232- // of at least 1 to avoid two allocations having the same base address.
233- // (The logic in `alloc_id_from_addr` assumes unique addresses, and different
234- // function/vtable pointers need to be distinguishable!)
235- global_state. next_base_addr = base_addr
236- . checked_add ( max ( size. bytes ( ) , 1 ) )
237- . ok_or_else ( || err_exhaust ! ( AddressSpaceFull ) ) ?;
238- // Even if `Size` didn't overflow, we might still have filled up the address space.
239- if global_state. next_base_addr > ecx. target_usize_max ( ) {
240- throw_exhaust ! ( AddressSpaceFull ) ;
241- }
242-
243- base_addr
244- } ;
245- trace ! (
246- "Assigning base address {:#x} to allocation {:?} (size: {}, align: {})" ,
247- base_addr,
248- alloc_id,
249- size. bytes( ) ,
250- align. bytes( ) ,
251- ) ;
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) ;
252258
253259 // Store address in cache.
254- entry . insert ( base_addr) ;
260+ global_state . base_addr . try_insert ( alloc_id , base_addr) . unwrap ( ) ;
255261
256262 // Also maintain the opposite mapping in `int_to_ptr_map`, ensuring we keep it sorted.
257263 // We have a fast-path for the common case that this address is bigger than all previous ones.
@@ -269,9 +275,9 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
269275 } ;
270276 global_state. int_to_ptr_map . insert ( pos, ( base_addr, alloc_id) ) ;
271277
272- base_addr
278+ Ok ( base_addr)
273279 }
274- } )
280+ }
275281 }
276282}
277283
@@ -359,16 +365,23 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
359365
360366 // This returns some prepared `MiriAllocBytes`, either because `addr_from_alloc_id` reserved
361367 // memory space in the past, or by doing the pre-allocation right upon being called.
362- fn get_global_alloc_bytes ( & self , id : AllocId , kind : MemoryKind , bytes : & [ u8 ] , align : Align ) -> InterpResult < ' tcx , MiriAllocBytes > {
368+ fn get_global_alloc_bytes (
369+ & self ,
370+ id : AllocId ,
371+ kind : MemoryKind ,
372+ bytes : & [ u8 ] ,
373+ align : Align ,
374+ ) -> InterpResult < ' tcx , MiriAllocBytes > {
363375 let ecx = self . eval_context_ref ( ) ;
364- Ok ( if ecx. machine . native_lib . is_some ( ) {
376+ if ecx. machine . native_lib . is_some ( ) {
365377 // In native lib mode, MiriAllocBytes for global allocations are handled via `prepared_alloc_bytes`.
366- // This additional call ensures that some `MiriAllocBytes` are always prepared.
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.
367380 ecx. addr_from_alloc_id ( id, kind) ?;
368- let mut global_state = ecx. machine . alloc_addresses . borrow_mut ( ) ;
369381 // The memory we need here will have already been allocated during an earlier call to
370382 // `addr_from_alloc_id` for this allocation. So don't create a new `MiriAllocBytes` here, instead
371383 // fetch the previously prepared bytes from `prepared_alloc_bytes`.
384+ let mut global_state = ecx. machine . alloc_addresses . borrow_mut ( ) ;
372385 let mut prepared_alloc_bytes = global_state
373386 . prepared_alloc_bytes
374387 . remove ( & id)
@@ -378,10 +391,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
378391 assert_eq ! ( prepared_alloc_bytes. len( ) , bytes. len( ) ) ;
379392 // Copy allocation contents into prepared memory.
380393 prepared_alloc_bytes. copy_from_slice ( bytes) ;
381- prepared_alloc_bytes
394+ Ok ( prepared_alloc_bytes)
382395 } else {
383- MiriAllocBytes :: from_bytes ( std:: borrow:: Cow :: Borrowed ( & * bytes) , align)
384- } )
396+ Ok ( MiriAllocBytes :: from_bytes ( std:: borrow:: Cow :: Borrowed ( bytes) , align) )
397+ }
385398 }
386399
387400 /// When a pointer is used for a memory access, this computes where in which allocation the
0 commit comments