Skip to content

Commit edc13e6

Browse files
committed
pthread: replace INIT_COOKIE by sync object metadata that gets cleared on writes
1 parent 7711eb9 commit edc13e6

21 files changed

+326
-129
lines changed

src/tools/miri/src/concurrency/sync.rs

Lines changed: 133 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,42 @@
1+
use std::any::Any;
12
use std::cell::RefCell;
23
use std::collections::VecDeque;
34
use std::collections::hash_map::Entry;
45
use std::default::Default;
56
use std::ops::Not;
67
use std::rc::Rc;
78
use std::time::Duration;
9+
use std::{fmt, iter};
810

911
use rustc_abi::Size;
1012
use rustc_data_structures::fx::FxHashMap;
1113

1214
use super::vector_clock::VClock;
1315
use crate::*;
1416

17+
/// A trait for the synchronization metadata that can be attached to a memory location.
18+
pub trait SyncObj: Any {
19+
/// Determines whether this object's metadata shall be deleted when a write to its
20+
/// location occurs.
21+
fn delete_on_write(&self) -> bool {
22+
false
23+
}
24+
}
25+
26+
impl dyn SyncObj {
27+
#[inline(always)]
28+
pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
29+
let x: &dyn Any = self;
30+
x.downcast_ref()
31+
}
32+
}
33+
34+
impl fmt::Debug for dyn SyncObj {
35+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36+
f.debug_struct("SyncObj").finish_non_exhaustive()
37+
}
38+
}
39+
1540
/// The mutex state.
1641
#[derive(Default, Debug)]
1742
struct Mutex {
@@ -214,15 +239,15 @@ pub(super) trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
214239

215240
impl<'tcx> AllocExtra<'tcx> {
216241
fn get_sync<T: 'static>(&self, offset: Size) -> Option<&T> {
217-
self.sync.get(&offset).and_then(|s| s.downcast_ref::<T>())
242+
self.sync_objs.get(&offset).and_then(|s| s.downcast_ref::<T>())
218243
}
219244
}
220245

221-
/// We designate an `init`` field in all primitives.
222-
/// If `init` is set to this, we consider the primitive initialized.
246+
/// We designate an `init`` field in all synchronization objects.
247+
/// If `init` is set to this, we consider the object initialized.
223248
pub const LAZY_INIT_COOKIE: u32 = 0xcafe_affe;
224249

225-
// Public interface to synchronization primitives. Please note that in most
250+
// Public interface to synchronization objects. Please note that in most
226251
// cases, the function calls are infallible and it is the client's (shim
227252
// implementation's) responsibility to detect and deal with erroneous
228253
// situations.
@@ -231,9 +256,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
231256
/// Helper for lazily initialized `alloc_extra.sync` data:
232257
/// this forces an immediate init.
233258
/// Return a reference to the data in the machine state.
234-
fn lazy_sync_init<'a, T: 'static>(
259+
fn lazy_sync_init<'a, T: SyncObj>(
235260
&'a mut self,
236-
primitive: &MPlaceTy<'tcx>,
261+
obj: &MPlaceTy<'tcx>,
237262
init_offset: Size,
238263
data: T,
239264
) -> InterpResult<'tcx, &'a T>
@@ -242,27 +267,28 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
242267
{
243268
let this = self.eval_context_mut();
244269

245-
let (alloc, offset, _) = this.ptr_get_alloc_id(primitive.ptr(), 0)?;
246-
let (alloc_extra, _machine) = this.get_alloc_extra_mut(alloc)?;
247-
alloc_extra.sync.insert(offset, Box::new(data));
270+
let (alloc, offset, _) = this.ptr_get_alloc_id(obj.ptr(), 0)?;
248271
// Mark this as "initialized".
249272
let init_cookie = Scalar::from_u32(LAZY_INIT_COOKIE);
250-
assert!(init_offset + init_cookie.size() <= primitive.layout.size);
251-
let init_field = primitive.offset(init_offset, this.machine.layouts.u32, this)?;
273+
assert!(init_offset + init_cookie.size() <= obj.layout.size);
274+
let init_field = obj.offset(init_offset, this.machine.layouts.u32, this)?;
252275
this.write_scalar_atomic(init_cookie, &init_field, AtomicWriteOrd::Relaxed)?;
276+
// Insert sync obj, and return reference to it.
277+
let (alloc_extra, _machine) = this.get_alloc_extra_mut(alloc)?;
278+
alloc_extra.sync_objs.insert(offset, Box::new(data));
253279
interp_ok(this.get_alloc_extra(alloc)?.get_sync::<T>(offset).unwrap())
254280
}
255281

256282
/// Helper for lazily initialized `alloc_extra.sync` data:
257-
/// Checks if the primitive is initialized:
283+
/// Checks if the synchronization object is initialized:
258284
/// - If yes, fetches the data from `alloc_extra.sync`, or calls `missing_data` if that fails
259285
/// and stores that in `alloc_extra.sync`.
260-
/// - Otherwise, calls `new_data` to initialize the primitive.
286+
/// - Otherwise, calls `new_data` to initialize the object.
261287
///
262288
/// Return a reference to the data in the machine state.
263-
fn lazy_sync_get_data<'a, T: 'static>(
289+
fn lazy_sync_get_data<'a, T: SyncObj>(
264290
&'a mut self,
265-
primitive: &MPlaceTy<'tcx>,
291+
obj: &MPlaceTy<'tcx>,
266292
init_offset: Size,
267293
missing_data: impl FnOnce() -> InterpResult<'tcx, T>,
268294
new_data: impl FnOnce(&mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, T>,
@@ -276,8 +302,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
276302
// thread initializing. Needs to be an RMW operation to ensure we read the *latest* value.
277303
// So we just try to replace MUTEX_INIT_COOKIE with itself.
278304
let init_cookie = Scalar::from_u32(LAZY_INIT_COOKIE);
279-
assert!(init_offset + init_cookie.size() <= primitive.layout.size);
280-
let init_field = primitive.offset(init_offset, this.machine.layouts.u32, this)?;
305+
assert!(init_offset + init_cookie.size() <= obj.layout.size);
306+
let init_field = obj.offset(init_offset, this.machine.layouts.u32, this)?;
281307
let (_init, success) = this
282308
.atomic_compare_exchange_scalar(
283309
&init_field,
@@ -290,27 +316,27 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
290316
.to_scalar_pair();
291317

292318
if success.to_bool()? {
293-
// If it is initialized, it must be found in the "sync primitive" table,
319+
// If it is initialized, it must be found in the "sync obj" table,
294320
// or else it has been moved illegally.
295-
let (alloc, offset, _) = this.ptr_get_alloc_id(primitive.ptr(), 0)?;
321+
let (alloc, offset, _) = this.ptr_get_alloc_id(obj.ptr(), 0)?;
296322
let (alloc_extra, _machine) = this.get_alloc_extra_mut(alloc)?;
297323
// Due to borrow checker reasons, we have to do the lookup twice.
298324
if alloc_extra.get_sync::<T>(offset).is_none() {
299325
let data = missing_data()?;
300-
alloc_extra.sync.insert(offset, Box::new(data));
326+
alloc_extra.sync_objs.insert(offset, Box::new(data));
301327
}
302328
interp_ok(alloc_extra.get_sync::<T>(offset).unwrap())
303329
} else {
304330
let data = new_data(this)?;
305-
this.lazy_sync_init(primitive, init_offset, data)
331+
this.lazy_sync_init(obj, init_offset, data)
306332
}
307333
}
308334

309-
/// Get the synchronization primitive associated with the given pointer,
335+
/// Get the synchronization object associated with the given pointer,
310336
/// or initialize a new one.
311337
///
312338
/// Return `None` if this pointer does not point to at least 1 byte of mutable memory.
313-
fn get_sync_or_init<'a, T: 'static>(
339+
fn get_sync_or_init<'a, T: SyncObj>(
314340
&'a mut self,
315341
ptr: Pointer,
316342
new: impl FnOnce(&'a mut MiriMachine<'tcx>) -> T,
@@ -331,11 +357,94 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
331357
// Due to borrow checker reasons, we have to do the lookup twice.
332358
if alloc_extra.get_sync::<T>(offset).is_none() {
333359
let new = new(machine);
334-
alloc_extra.sync.insert(offset, Box::new(new));
360+
alloc_extra.sync_objs.insert(offset, Box::new(new));
335361
}
336362
Some(alloc_extra.get_sync::<T>(offset).unwrap())
337363
}
338364

365+
/// Helper for "immovable" synchronization objects: the expected protocol for these objects is
366+
/// that they use a static initializer of `uninit_val`, and we set them to `init_val` upon
367+
/// initialization. At that point we also register a synchronization object, which is expected
368+
/// to have `delete_on_write() == true`. So in the future, if we still see the object, we know
369+
/// the location must still contain `init_val`. If the object is copied somewhere, that will
370+
/// show up as a non-`init_val` value without a synchronization object, which we can then use to
371+
/// error.
372+
///
373+
/// `new_meta_obj` gets invoked when there is not yet an initialization object.
374+
/// It has to ensure that the in-memory representation indeed matches `uninit_val`.
375+
fn get_immovable_sync_with_static_init<'a, T: SyncObj>(
376+
&'a mut self,
377+
obj: &MPlaceTy<'tcx>,
378+
init_offset: Size,
379+
uninit_val: u8,
380+
init_val: u8,
381+
new_meta_obj: impl FnOnce(&mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, T>,
382+
) -> InterpResult<'tcx, &'a T>
383+
where
384+
'tcx: 'a,
385+
{
386+
let this = self.eval_context_mut();
387+
this.check_ptr_access(obj.ptr(), obj.layout.size, CheckInAllocMsg::Dereferenceable)?;
388+
assert!(init_offset < obj.layout.size); // ensure our 1-byte flag fits
389+
let init_field = obj.offset(init_offset, this.machine.layouts.u8, this)?;
390+
391+
let (alloc, offset, _) = this.ptr_get_alloc_id(init_field.ptr(), 0)?;
392+
let (alloc_extra, _machine) = this.get_alloc_extra_mut(alloc)?;
393+
// Due to borrow checker reasons, we have to do the lookup twice.
394+
if alloc_extra.get_sync::<T>(offset).is_some() {
395+
let (alloc_extra, _machine) = this.get_alloc_extra_mut(alloc).unwrap();
396+
return interp_ok(alloc_extra.get_sync::<T>(offset).unwrap());
397+
}
398+
399+
// There's no sync object there yet. Create one, and try a CAS for uninit_val to init_val.
400+
let meta_obj = new_meta_obj(this)?;
401+
let (_init, success) = this
402+
.atomic_compare_exchange_scalar(
403+
&init_field,
404+
&ImmTy::from_scalar(Scalar::from_u8(uninit_val), this.machine.layouts.u8),
405+
Scalar::from_u8(init_val),
406+
AtomicRwOrd::Relaxed,
407+
AtomicReadOrd::Relaxed,
408+
/* can_fail_spuriously */ false,
409+
)?
410+
.to_scalar_pair();
411+
assert!(success.to_bool()?, "`new_meta_obj` should have ensured that this CAS succeeds.");
412+
413+
let (alloc_extra, _machine) = this.get_alloc_extra_mut(alloc).unwrap();
414+
assert!(meta_obj.delete_on_write());
415+
alloc_extra.sync_objs.insert(offset, Box::new(meta_obj));
416+
interp_ok(alloc_extra.get_sync::<T>(offset).unwrap())
417+
}
418+
419+
/// Explicitly initializes an object that would usually be implicitly initialized with
420+
/// `get_immovable_sync_with_static_init`.
421+
fn init_immovable_sync<'a, T: SyncObj>(
422+
&'a mut self,
423+
obj: &MPlaceTy<'tcx>,
424+
init_offset: Size,
425+
init_val: u8,
426+
new_meta_obj: T,
427+
) -> InterpResult<'tcx, Option<&'a T>>
428+
where
429+
'tcx: 'a,
430+
{
431+
let this = self.eval_context_mut();
432+
this.check_ptr_access(obj.ptr(), obj.layout.size, CheckInAllocMsg::Dereferenceable)?;
433+
assert!(init_offset < obj.layout.size); // ensure our 1-byte flag fits
434+
let init_field = obj.offset(init_offset, this.machine.layouts.u8, this)?;
435+
436+
// Zero the entire object, and then store `init_val` directly.
437+
this.write_bytes_ptr(obj.ptr(), iter::repeat_n(0, obj.layout.size.bytes_usize()))?;
438+
this.write_scalar(Scalar::from_u8(init_val), &init_field)?;
439+
440+
// Create meta-level initialization object.
441+
let (alloc, offset, _) = this.ptr_get_alloc_id(init_field.ptr(), 0)?;
442+
let (alloc_extra, _machine) = this.get_alloc_extra_mut(alloc).unwrap();
443+
assert!(new_meta_obj.delete_on_write());
444+
alloc_extra.sync_objs.insert(offset, Box::new(new_meta_obj));
445+
interp_ok(Some(alloc_extra.get_sync::<T>(offset).unwrap()))
446+
}
447+
339448
/// Lock by setting the mutex owner and increasing the lock count.
340449
fn mutex_lock(&mut self, mutex_ref: &MutexRef) -> InterpResult<'tcx> {
341450
let this = self.eval_context_mut();

src/tools/miri/src/machine.rs

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
//! Global machine state as well as implementation of the interpreter engine
22
//! `Machine` trait.
33
4-
use std::any::Any;
54
use std::borrow::Cow;
65
use std::cell::{Cell, RefCell};
6+
use std::collections::BTreeMap;
77
use std::path::Path;
88
use std::rc::Rc;
99
use std::{fmt, process};
@@ -36,6 +36,7 @@ use rustc_target::spec::Arch;
3636
use crate::alloc_addresses::EvalContextExt;
3737
use crate::concurrency::cpu_affinity::{self, CpuAffinityMask};
3838
use crate::concurrency::data_race::{self, NaReadType, NaWriteType};
39+
use crate::concurrency::sync::SyncObj;
3940
use crate::concurrency::{
4041
AllocDataRaceHandler, GenmcCtx, GenmcEvalContextExt as _, GlobalDataRaceHandler, weak_memory,
4142
};
@@ -399,11 +400,11 @@ pub struct AllocExtra<'tcx> {
399400
/// if this allocation is leakable. The backtrace is not
400401
/// pruned yet; that should be done before printing it.
401402
pub backtrace: Option<Vec<FrameInfo<'tcx>>>,
402-
/// Synchronization primitives like to attach extra data to particular addresses. We store that
403+
/// Synchronization objects like to attach extra data to particular addresses. We store that
403404
/// inside the relevant allocation, to ensure that everything is removed when the allocation is
404405
/// freed.
405406
/// This maps offsets to synchronization-primitive-specific data.
406-
pub sync: FxHashMap<Size, Box<dyn Any>>,
407+
pub sync_objs: BTreeMap<Size, Box<dyn SyncObj>>,
407408
}
408409

409410
// We need a `Clone` impl because the machine passes `Allocation` through `Cow`...
@@ -416,7 +417,7 @@ impl<'tcx> Clone for AllocExtra<'tcx> {
416417

417418
impl VisitProvenance for AllocExtra<'_> {
418419
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
419-
let AllocExtra { borrow_tracker, data_race, backtrace: _, sync: _ } = self;
420+
let AllocExtra { borrow_tracker, data_race, backtrace: _, sync_objs: _ } = self;
420421

421422
borrow_tracker.visit_provenance(visit);
422423
data_race.visit_provenance(visit);
@@ -991,7 +992,12 @@ impl<'tcx> MiriMachine<'tcx> {
991992
.insert(id, (ecx.machine.current_user_relevant_span(), None));
992993
}
993994

994-
interp_ok(AllocExtra { borrow_tracker, data_race, backtrace, sync: FxHashMap::default() })
995+
interp_ok(AllocExtra {
996+
borrow_tracker,
997+
data_race,
998+
backtrace,
999+
sync_objs: BTreeMap::default(),
1000+
})
9951001
}
9961002
}
9971003

@@ -1581,6 +1587,18 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
15811587
if let Some(borrow_tracker) = &mut alloc_extra.borrow_tracker {
15821588
borrow_tracker.before_memory_write(alloc_id, prov_extra, range, machine)?;
15831589
}
1590+
// Delete sync objects that don't like writes.
1591+
// Most of the time, we can just skip this.
1592+
if !alloc_extra.sync_objs.is_empty() {
1593+
let to_delete = alloc_extra
1594+
.sync_objs
1595+
.range(range.start..range.end())
1596+
.filter_map(|(offset, obj)| obj.delete_on_write().then_some(*offset))
1597+
.collect::<Vec<_>>();
1598+
for offset in to_delete {
1599+
alloc_extra.sync_objs.remove(&offset);
1600+
}
1601+
}
15841602
interp_ok(())
15851603
}
15861604

src/tools/miri/src/shims/unix/freebsd/sync.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ use core::time::Duration;
44

55
use rustc_abi::FieldIdx;
66

7-
use crate::concurrency::sync::FutexRef;
7+
use crate::concurrency::sync::{FutexRef, SyncObj};
88
use crate::*;
99

1010
pub struct FreeBsdFutex {
1111
futex: FutexRef,
1212
}
1313

14+
impl SyncObj for FreeBsdFutex {}
15+
1416
/// Extended variant of the `timespec` struct.
1517
pub struct UmtxTime {
1618
timeout: Duration,

src/tools/miri/src/shims/unix/linux_like/sync.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
use crate::concurrency::sync::FutexRef;
1+
use crate::concurrency::sync::{FutexRef, SyncObj};
22
use crate::shims::sig::check_min_vararg_count;
33
use crate::*;
44

55
struct LinuxFutex {
66
futex: FutexRef,
77
}
88

9+
impl SyncObj for LinuxFutex {}
10+
911
/// Implementation of the SYS_futex syscall.
1012
/// `args` is the arguments *including* the syscall number.
1113
pub fn futex<'tcx>(

src/tools/miri/src/shims/unix/macos/sync.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use std::time::Duration;
1515

1616
use rustc_abi::Size;
1717

18-
use crate::concurrency::sync::FutexRef;
18+
use crate::concurrency::sync::{FutexRef, SyncObj};
1919
use crate::*;
2020

2121
#[derive(Clone)]
@@ -24,6 +24,8 @@ enum MacOsUnfairLock {
2424
Active { mutex_ref: MutexRef },
2525
}
2626

27+
impl SyncObj for MacOsUnfairLock {}
28+
2729
pub enum MacOsFutexTimeout<'a, 'tcx> {
2830
None,
2931
Relative { clock_op: &'a OpTy<'tcx>, timeout_op: &'a OpTy<'tcx> },
@@ -44,6 +46,8 @@ struct MacOsFutex {
4446
shared: Cell<bool>,
4547
}
4648

49+
impl SyncObj for MacOsFutex {}
50+
4751
impl<'tcx> EvalContextExtPriv<'tcx> for crate::MiriInterpCx<'tcx> {}
4852
trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
4953
fn os_unfair_lock_get_data<'a>(

0 commit comments

Comments
 (0)