Skip to content

Commit d7d6b8c

Browse files
committed
Auto merge of #3854 - rust-lang:rustup-2024-09-01, r=RalfJung
Automatic Rustup
2 parents 7b9a16a + 6f15140 commit d7d6b8c

File tree

15 files changed

+341
-75
lines changed

15 files changed

+341
-75
lines changed

rust-version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0d634185dfddefe09047881175f35c65d68dcff1
1+
43eaa5c6246057b1675f42631967ad500eaf47d5

src/alloc_addresses/mod.rs

Lines changed: 140 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ mod reuse_pool;
55

66
use std::cell::RefCell;
77
use std::cmp::max;
8-
use std::collections::hash_map::Entry;
98

109
use rand::Rng;
1110

@@ -42,6 +41,11 @@ pub struct GlobalStateInner {
4241
/// they do not have an `AllocExtra`.
4342
/// This is the inverse of `int_to_ptr_map`.
4443
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>,
4549
/// A pool of addresses we can reuse for future allocations.
4650
reuse: ReusePool,
4751
/// Whether an allocation has been exposed or not. This cannot be put
@@ -59,6 +63,7 @@ impl VisitProvenance for GlobalStateInner {
5963
let GlobalStateInner {
6064
int_to_ptr_map: _,
6165
base_addr: _,
66+
prepared_alloc_bytes: _,
6267
reuse: _,
6368
exposed: _,
6469
next_base_addr: _,
@@ -78,6 +83,7 @@ impl GlobalStateInner {
7883
GlobalStateInner {
7984
int_to_ptr_map: Vec::default(),
8085
base_addr: FxHashMap::default(),
86+
prepared_alloc_bytes: FxHashMap::default(),
8187
reuse: ReusePool::new(config),
8288
exposed: FxHashSet::default(),
8389
next_base_addr: stack_addr,
@@ -144,6 +150,95 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
144150
}
145151
}
146152

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+
147242
fn addr_from_alloc_id(
148243
&self,
149244
alloc_id: AllocId,
@@ -153,66 +248,16 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
153248
let mut global_state = ecx.machine.alloc_addresses.borrow_mut();
154249
let global_state = &mut *global_state;
155250

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);
213258

214259
// Store address in cache.
215-
entry.insert(base_addr);
260+
global_state.base_addr.try_insert(alloc_id, base_addr).unwrap();
216261

217262
// Also maintain the opposite mapping in `int_to_ptr_map`, ensuring we keep it sorted.
218263
// 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> {
230275
};
231276
global_state.int_to_ptr_map.insert(pos, (base_addr, alloc_id));
232277

233-
base_addr
278+
Ok(base_addr)
234279
}
235-
})
280+
}
236281
}
237282
}
238283

@@ -318,6 +363,40 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
318363
Ok(base_ptr.wrapping_offset(offset, ecx))
319364
}
320365

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+
321400
/// When a pointer is used for a memory access, this computes where in which allocation the
322401
/// access is going.
323402
fn ptr_get_alloc(

src/concurrency/thread.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -888,8 +888,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
888888
}
889889
let alloc = this.ctfe_query(|tcx| tcx.eval_static_initializer(def_id))?;
890890
// We make a full copy of this allocation.
891-
let mut alloc =
892-
alloc.inner().adjust_from_tcx(&this.tcx, |ptr| this.global_root_pointer(ptr))?;
891+
let mut alloc = alloc.inner().adjust_from_tcx(
892+
&this.tcx,
893+
|bytes, align| {
894+
Ok(MiriAllocBytes::from_bytes(std::borrow::Cow::Borrowed(bytes), align))
895+
},
896+
|ptr| this.global_root_pointer(ptr),
897+
)?;
893898
// This allocation will be deallocated when the thread dies, so it is not in read-only memory.
894899
alloc.mutability = Mutability::Mut;
895900
// Create a fresh allocation with this content.

src/helpers.rs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -630,14 +630,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
630630
self.ecx
631631
}
632632

633-
fn aggregate_field_order(memory_index: &IndexVec<FieldIdx, u32>, idx: usize) -> usize {
634-
// We need to do an *inverse* lookup: find the field that has position `idx` in memory order.
635-
for (src_field, &mem_pos) in memory_index.iter_enumerated() {
636-
if mem_pos as usize == idx {
637-
return src_field.as_usize();
638-
}
639-
}
640-
panic!("invalid `memory_index`, could not find {}-th field in memory order", idx);
633+
fn aggregate_field_iter(
634+
memory_index: &IndexVec<FieldIdx, u32>,
635+
) -> impl Iterator<Item = FieldIdx> + 'static {
636+
let inverse_memory_index = memory_index.invert_bijective_mapping();
637+
inverse_memory_index.into_iter()
641638
}
642639

643640
// Hook to detect `UnsafeCell`.

src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
#![feature(let_chains)]
1313
#![feature(trait_upcasting)]
1414
#![feature(strict_overflow_ops)]
15+
#![feature(strict_provenance)]
16+
#![feature(exposed_provenance)]
17+
#![feature(pointer_is_aligned_to)]
1518
// Configure clippy and other lints
1619
#![allow(
1720
clippy::collapsible_else_if,

src/machine.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Global machine state as well as implementation of the interpreter engine
22
//! `Machine` trait.
33
4+
use std::borrow::Cow;
45
use std::cell::RefCell;
56
use std::collections::hash_map::Entry;
67
use std::fmt;
@@ -1261,6 +1262,30 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
12611262
})
12621263
}
12631264

1265+
/// Called to adjust global allocations to the Provenance and AllocExtra of this machine.
1266+
///
1267+
/// If `alloc` contains pointers, then they are all pointing to globals.
1268+
///
1269+
/// This should avoid copying if no work has to be done! If this returns an owned
1270+
/// allocation (because a copy had to be done to adjust things), machine memory will
1271+
/// cache the result. (This relies on `AllocMap::get_or` being able to add the
1272+
/// owned allocation to the map even when the map is shared.)
1273+
fn adjust_global_allocation<'b>(
1274+
ecx: &InterpCx<'tcx, Self>,
1275+
id: AllocId,
1276+
alloc: &'b Allocation,
1277+
) -> InterpResult<'tcx, Cow<'b, Allocation<Self::Provenance, Self::AllocExtra, Self::Bytes>>>
1278+
{
1279+
let kind = Self::GLOBAL_KIND.unwrap().into();
1280+
let alloc = alloc.adjust_from_tcx(
1281+
&ecx.tcx,
1282+
|bytes, align| ecx.get_global_alloc_bytes(id, kind, bytes, align),
1283+
|ptr| ecx.global_root_pointer(ptr),
1284+
)?;
1285+
let extra = Self::init_alloc_extra(ecx, id, kind, alloc.size(), alloc.align)?;
1286+
Ok(Cow::Owned(alloc.with_extra(extra)))
1287+
}
1288+
12641289
#[inline(always)]
12651290
fn before_memory_read(
12661291
_tcx: TyCtxtAt<'tcx>,

src/shims/native_lib.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,8 @@ enum CArg {
194194
UInt64(u64),
195195
/// usize.
196196
USize(usize),
197+
/// Raw pointer, stored as C's `void*`.
198+
RawPtr(*mut std::ffi::c_void),
197199
}
198200

199201
impl<'a> CArg {
@@ -210,6 +212,7 @@ impl<'a> CArg {
210212
CArg::UInt32(i) => ffi::arg(i),
211213
CArg::UInt64(i) => ffi::arg(i),
212214
CArg::USize(i) => ffi::arg(i),
215+
CArg::RawPtr(i) => ffi::arg(i),
213216
}
214217
}
215218
}
@@ -234,6 +237,19 @@ fn imm_to_carg<'tcx>(v: ImmTy<'tcx>, cx: &impl HasDataLayout) -> InterpResult<'t
234237
ty::Uint(UintTy::U64) => CArg::UInt64(v.to_scalar().to_u64()?),
235238
ty::Uint(UintTy::Usize) =>
236239
CArg::USize(v.to_scalar().to_target_usize(cx)?.try_into().unwrap()),
240+
ty::RawPtr(_, mutability) => {
241+
// Arbitrary mutable pointer accesses are not currently supported in Miri.
242+
if mutability.is_mut() {
243+
throw_unsup_format!(
244+
"unsupported mutable pointer type for native call: {}",
245+
v.layout.ty
246+
);
247+
} else {
248+
let s = v.to_scalar().to_pointer(cx)?.addr();
249+
// This relies on the `expose_provenance` in `addr_from_alloc_id`.
250+
CArg::RawPtr(std::ptr::with_exposed_provenance_mut(s.bytes_usize()))
251+
}
252+
}
237253
_ => throw_unsup_format!("unsupported argument type for native call: {}", v.layout.ty),
238254
})
239255
}

0 commit comments

Comments
 (0)