Small changes.

This commit is contained in:
Vytautas Astrauskas 2020-05-18 17:18:15 +02:00
parent 3da61fa427
commit fdfd56b75b
5 changed files with 143 additions and 61 deletions

View File

@ -202,6 +202,7 @@ fn condattr_set_clock_id<'mir, 'tcx: 'mir>(
// Our chosen memory layout for the emulated conditional variable (does not have // Our chosen memory layout for the emulated conditional variable (does not have
// to match the platform layout!): // to match the platform layout!):
// bytes 0-3: reserved for signature on macOS
// bytes 4-7: the conditional variable id as u32 or 0 if id is not assigned yet. // bytes 4-7: the conditional variable id as u32 or 0 if id is not assigned yet.
// bytes 8-11: the clock id constant as i32 // bytes 8-11: the clock id constant as i32
@ -275,19 +276,13 @@ fn release_cond_mutex<'mir, 'tcx: 'mir>(
active_thread: ThreadId, active_thread: ThreadId,
mutex: MutexId, mutex: MutexId,
) -> InterpResult<'tcx> { ) -> InterpResult<'tcx> {
if let Some((owner_thread, current_locked_count)) = ecx.mutex_unlock(mutex) { if let Some((old_owner_thread, old_locked_count)) = ecx.mutex_unlock(mutex)? {
if current_locked_count != 0 { if old_locked_count != 1 {
throw_unsup_format!("awaiting on multiple times acquired lock is not supported"); throw_unsup_format!("awaiting on a lock acquired multiple times is not supported");
} }
if owner_thread != active_thread { if old_owner_thread != active_thread {
throw_ub_format!("awaiting on a mutex owned by a different thread"); throw_ub_format!("awaiting on a mutex owned by a different thread");
} }
if let Some(thread) = ecx.mutex_dequeue(mutex) {
// We have at least one thread waiting on this mutex. Transfer
// ownership to it.
ecx.mutex_lock(mutex, thread);
ecx.unblock_thread(thread)?;
}
} else { } else {
throw_ub_format!("awaiting on unlocked mutex"); throw_ub_format!("awaiting on unlocked mutex");
} }
@ -349,7 +344,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
mutexattr_get_kind(this, attr_op)?.not_undef()? mutexattr_get_kind(this, attr_op)?.not_undef()?
}; };
let _ = mutex_get_or_create_id(this, mutex_op)?; // Write 0 to use the same code path as the static initializers.
mutex_set_id(this, mutex_op, Scalar::from_i32(0))?;
mutex_set_kind(this, mutex_op, kind)?; mutex_set_kind(this, mutex_op, kind)?;
Ok(0) Ok(0)
@ -427,19 +424,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
let kind = mutex_get_kind(this, mutex_op)?.not_undef()?; let kind = mutex_get_kind(this, mutex_op)?.not_undef()?;
let id = mutex_get_or_create_id(this, mutex_op)?; let id = mutex_get_or_create_id(this, mutex_op)?;
if let Some((owner_thread, current_locked_count)) = this.mutex_unlock(id) { if let Some((old_owner_thread, _old_locked_count)) = this.mutex_unlock(id)? {
if owner_thread != this.get_active_thread()? { if old_owner_thread != this.get_active_thread()? {
throw_ub_format!("called pthread_mutex_unlock on a mutex owned by another thread"); throw_ub_format!("called pthread_mutex_unlock on a mutex owned by another thread");
} }
if current_locked_count == 0 {
// The mutex is unlocked.
if let Some(thread) = this.mutex_dequeue(id) {
// We have at least one thread waiting on this mutex. Transfer
// ownership to it.
this.mutex_lock(id, thread);
this.unblock_thread(thread)?;
}
}
Ok(0) Ok(0)
} else { } else {
if kind == this.eval_libc("PTHREAD_MUTEX_NORMAL")? { if kind == this.eval_libc("PTHREAD_MUTEX_NORMAL")? {
@ -476,11 +464,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
let active_thread = this.get_active_thread()?; let active_thread = this.get_active_thread()?;
if this.rwlock_is_write_locked(id) { if this.rwlock_is_write_locked(id) {
this.rwlock_enqueue_reader(id, active_thread); this.rwlock_enqueue_and_block_reader(id, active_thread)?;
this.block_thread(active_thread)?;
Ok(0) Ok(0)
} else { } else {
this.rwlock_reader_add(id, active_thread); this.rwlock_reader_lock(id, active_thread);
Ok(0) Ok(0)
} }
} }
@ -494,7 +481,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
if this.rwlock_is_write_locked(id) { if this.rwlock_is_write_locked(id) {
this.eval_libc_i32("EBUSY") this.eval_libc_i32("EBUSY")
} else { } else {
this.rwlock_reader_add(id, active_thread); this.rwlock_reader_lock(id, active_thread);
Ok(0) Ok(0)
} }
} }
@ -506,10 +493,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
let active_thread = this.get_active_thread()?; let active_thread = this.get_active_thread()?;
if this.rwlock_is_locked(id) { if this.rwlock_is_locked(id) {
this.block_thread(active_thread)?; this.rwlock_enqueue_and_block_writer(id, active_thread)?;
this.rwlock_enqueue_writer(id, active_thread);
} else { } else {
this.rwlock_writer_set(id, active_thread); this.rwlock_writer_lock(id, active_thread);
} }
Ok(0) Ok(0)
@ -524,7 +510,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
if this.rwlock_is_locked(id) { if this.rwlock_is_locked(id) {
this.eval_libc_i32("EBUSY") this.eval_libc_i32("EBUSY")
} else { } else {
this.rwlock_writer_set(id, active_thread); this.rwlock_writer_lock(id, active_thread);
Ok(0) Ok(0)
} }
} }
@ -535,18 +521,18 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
let id = rwlock_get_or_create_id(this, rwlock_op)?; let id = rwlock_get_or_create_id(this, rwlock_op)?;
let active_thread = this.get_active_thread()?; let active_thread = this.get_active_thread()?;
if this.rwlock_reader_remove(id, active_thread) { if this.rwlock_reader_unlock(id, active_thread) {
// The thread was a reader. // The thread was a reader.
if this.rwlock_is_locked(id) { if this.rwlock_is_locked(id) {
// No more readers owning the lock. Give it to a writer if there // No more readers owning the lock. Give it to a writer if there
// is any. // is any.
if let Some(writer) = this.rwlock_dequeue_writer(id) { if let Some(writer) = this.rwlock_dequeue_writer(id) {
this.unblock_thread(writer)?; this.unblock_thread(writer)?;
this.rwlock_writer_set(id, writer); this.rwlock_writer_lock(id, writer);
} }
} }
Ok(0) Ok(0)
} else if Some(active_thread) == this.rwlock_writer_remove(id) { } else if Some(active_thread) == this.rwlock_writer_unlock(id) {
// The thread was a writer. // The thread was a writer.
// //
// We are prioritizing writers here against the readers. As a // We are prioritizing writers here against the readers. As a
@ -555,12 +541,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
if let Some(writer) = this.rwlock_dequeue_writer(id) { if let Some(writer) = this.rwlock_dequeue_writer(id) {
// Give the lock to another writer. // Give the lock to another writer.
this.unblock_thread(writer)?; this.unblock_thread(writer)?;
this.rwlock_writer_set(id, writer); this.rwlock_writer_lock(id, writer);
} else { } else {
// Give the lock to all readers. // Give the lock to all readers.
while let Some(reader) = this.rwlock_dequeue_reader(id) { while let Some(reader) = this.rwlock_dequeue_reader(id) {
this.unblock_thread(reader)?; this.unblock_thread(reader)?;
this.rwlock_reader_add(id, reader); this.rwlock_reader_lock(id, reader);
} }
} }
Ok(0) Ok(0)
@ -586,6 +572,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
fn pthread_condattr_init(&mut self, attr_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> { fn pthread_condattr_init(&mut self, attr_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
let this = self.eval_context_mut(); let this = self.eval_context_mut();
// The default value of the clock attribute shall refer to the system
// clock.
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_condattr_setclock.html
let default_clock_id = this.eval_libc("CLOCK_REALTIME")?; let default_clock_id = this.eval_libc("CLOCK_REALTIME")?;
condattr_set_clock_id(this, attr_op, default_clock_id)?; condattr_set_clock_id(this, attr_op, default_clock_id)?;
@ -647,7 +636,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
condattr_get_clock_id(this, attr_op)?.not_undef()? condattr_get_clock_id(this, attr_op)?.not_undef()?
}; };
let _ = cond_get_or_create_id(this, cond_op)?; // Write 0 to use the same code path as the static initializers.
cond_set_id(this, cond_op, Scalar::from_i32(0))?;
cond_set_clock_id(this, cond_op, clock_id)?; cond_set_clock_id(this, cond_op, clock_id)?;
Ok(0) Ok(0)

View File

@ -1,6 +1,7 @@
use std::collections::{hash_map::Entry, HashMap, VecDeque}; use std::collections::{hash_map::Entry, HashMap, VecDeque};
use std::convert::TryFrom; use std::convert::TryFrom;
use std::num::NonZeroU32; use std::num::NonZeroU32;
use std::ops::Not;
use rustc_index::vec::{Idx, IndexVec}; use rustc_index::vec::{Idx, IndexVec};
@ -142,34 +143,47 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
mutex.lock_count = mutex.lock_count.checked_add(1).unwrap(); mutex.lock_count = mutex.lock_count.checked_add(1).unwrap();
} }
/// Unlock by decreasing the lock count. If the lock count reaches 0, unset /// Try unlocking by decreasing the lock count and returning the old owner
/// the owner. /// and the old lock count. If the lock count reaches 0, release the lock
fn mutex_unlock(&mut self, id: MutexId) -> Option<(ThreadId, usize)> { /// and potentially give to a new owner. If the lock was not locked, return
/// `None`.
///
/// Note: It is the caller's responsibility to check that the thread that
/// unlocked the lock actually is the same one, which owned it.
fn mutex_unlock(&mut self, id: MutexId) -> InterpResult<'tcx, Option<(ThreadId, usize)>> {
let this = self.eval_context_mut(); let this = self.eval_context_mut();
let mutex = &mut this.machine.threads.sync.mutexes[id]; let mutex = &mut this.machine.threads.sync.mutexes[id];
if let Some(current_owner) = mutex.owner { if let Some(current_owner) = mutex.owner {
mutex.lock_count = mutex // Mutex is locked.
.lock_count let old_lock_count = mutex.lock_count;
mutex.lock_count = old_lock_count
.checked_sub(1) .checked_sub(1)
.expect("invariant violation: lock_count == 0 iff the thread is unlocked"); .expect("invariant violation: lock_count == 0 iff the thread is unlocked");
if mutex.lock_count == 0 { if mutex.lock_count == 0 {
mutex.owner = None; mutex.owner = None;
// The mutex is completely unlocked. Try transfering ownership
// to another thread.
if let Some(new_owner) = this.mutex_dequeue(id) {
this.mutex_lock(id, new_owner);
this.unblock_thread(new_owner)?;
} }
Some((current_owner, mutex.lock_count)) }
Ok(Some((current_owner, old_lock_count)))
} else { } else {
None // Mutex is unlocked.
Ok(None)
} }
} }
#[inline] #[inline]
/// Take a thread out the queue waiting for the lock. /// Put the thread into the queue waiting for the lock.
fn mutex_enqueue(&mut self, id: MutexId, thread: ThreadId) { fn mutex_enqueue(&mut self, id: MutexId, thread: ThreadId) {
let this = self.eval_context_mut(); let this = self.eval_context_mut();
this.machine.threads.sync.mutexes[id].queue.push_back(thread); this.machine.threads.sync.mutexes[id].queue.push_back(thread);
} }
#[inline] #[inline]
/// Take a thread out the queue waiting for the lock. /// Take a thread out of the queue waiting for the lock.
fn mutex_dequeue(&mut self, id: MutexId) -> Option<ThreadId> { fn mutex_dequeue(&mut self, id: MutexId) -> Option<ThreadId> {
let this = self.eval_context_mut(); let this = self.eval_context_mut();
this.machine.threads.sync.mutexes[id].queue.pop_front() this.machine.threads.sync.mutexes[id].queue.pop_front()
@ -187,7 +201,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
fn rwlock_is_locked(&mut self, id: RwLockId) -> bool { fn rwlock_is_locked(&mut self, id: RwLockId) -> bool {
let this = self.eval_context_mut(); let this = self.eval_context_mut();
this.machine.threads.sync.rwlocks[id].writer.is_some() this.machine.threads.sync.rwlocks[id].writer.is_some()
|| !this.machine.threads.sync.rwlocks[id].readers.is_empty() || this.machine.threads.sync.rwlocks[id].readers.is_empty().not()
} }
#[inline] #[inline]
@ -197,16 +211,18 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
this.machine.threads.sync.rwlocks[id].writer.is_some() this.machine.threads.sync.rwlocks[id].writer.is_some()
} }
/// Add a reader that collectively with other readers owns the lock. /// Read-lock the lock by adding the `reader` the list of threads that own
fn rwlock_reader_add(&mut self, id: RwLockId, reader: ThreadId) { /// this lock.
fn rwlock_reader_lock(&mut self, id: RwLockId, reader: ThreadId) {
let this = self.eval_context_mut(); let this = self.eval_context_mut();
assert!(!this.rwlock_is_write_locked(id), "the lock is write locked"); assert!(!this.rwlock_is_write_locked(id), "the lock is write locked");
let count = this.machine.threads.sync.rwlocks[id].readers.entry(reader).or_insert(0); let count = this.machine.threads.sync.rwlocks[id].readers.entry(reader).or_insert(0);
*count += 1; *count = count.checked_add(1).expect("the reader counter overflowed");
} }
/// Try removing the reader. Returns `true` if succeeded. /// Try read-unlock the lock for `reader`. Returns `true` if succeeded,
fn rwlock_reader_remove(&mut self, id: RwLockId, reader: ThreadId) -> bool { /// `false` if this `reader` did not hold the lock.
fn rwlock_reader_unlock(&mut self, id: RwLockId, reader: ThreadId) -> bool {
let this = self.eval_context_mut(); let this = self.eval_context_mut();
match this.machine.threads.sync.rwlocks[id].readers.entry(reader) { match this.machine.threads.sync.rwlocks[id].readers.entry(reader) {
Entry::Occupied(mut entry) => { Entry::Occupied(mut entry) => {
@ -222,15 +238,20 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
} }
#[inline] #[inline]
/// Put the reader in the queue waiting for the lock. /// Put the reader in the queue waiting for the lock and block it.
fn rwlock_enqueue_reader(&mut self, id: RwLockId, reader: ThreadId) { fn rwlock_enqueue_and_block_reader(
&mut self,
id: RwLockId,
reader: ThreadId,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut(); let this = self.eval_context_mut();
assert!(this.rwlock_is_write_locked(id), "queueing on not write locked lock"); assert!(this.rwlock_is_write_locked(id), "queueing on not write locked lock");
this.machine.threads.sync.rwlocks[id].reader_queue.push_back(reader); this.machine.threads.sync.rwlocks[id].reader_queue.push_back(reader);
this.block_thread(reader)
} }
#[inline] #[inline]
/// Take the reader out the queue waiting for the lock. /// Take a reader out the queue waiting for the lock.
fn rwlock_dequeue_reader(&mut self, id: RwLockId) -> Option<ThreadId> { fn rwlock_dequeue_reader(&mut self, id: RwLockId) -> Option<ThreadId> {
let this = self.eval_context_mut(); let this = self.eval_context_mut();
this.machine.threads.sync.rwlocks[id].reader_queue.pop_front() this.machine.threads.sync.rwlocks[id].reader_queue.pop_front()
@ -238,25 +259,30 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
#[inline] #[inline]
/// Lock by setting the writer that owns the lock. /// Lock by setting the writer that owns the lock.
fn rwlock_writer_set(&mut self, id: RwLockId, writer: ThreadId) { fn rwlock_writer_lock(&mut self, id: RwLockId, writer: ThreadId) {
let this = self.eval_context_mut(); let this = self.eval_context_mut();
assert!(!this.rwlock_is_locked(id), "the lock is already locked"); assert!(!this.rwlock_is_locked(id), "the lock is already locked");
this.machine.threads.sync.rwlocks[id].writer = Some(writer); this.machine.threads.sync.rwlocks[id].writer = Some(writer);
} }
#[inline] #[inline]
/// Try removing the writer. /// Try to unlock by removing the writer.
fn rwlock_writer_remove(&mut self, id: RwLockId) -> Option<ThreadId> { fn rwlock_writer_unlock(&mut self, id: RwLockId) -> Option<ThreadId> {
let this = self.eval_context_mut(); let this = self.eval_context_mut();
this.machine.threads.sync.rwlocks[id].writer.take() this.machine.threads.sync.rwlocks[id].writer.take()
} }
#[inline] #[inline]
/// Put the writer in the queue waiting for the lock. /// Put the writer in the queue waiting for the lock.
fn rwlock_enqueue_writer(&mut self, id: RwLockId, writer: ThreadId) { fn rwlock_enqueue_and_block_writer(
&mut self,
id: RwLockId,
writer: ThreadId,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut(); let this = self.eval_context_mut();
assert!(this.rwlock_is_locked(id), "queueing on unlocked lock"); assert!(this.rwlock_is_locked(id), "queueing on unlocked lock");
this.machine.threads.sync.rwlocks[id].writer_queue.push_back(writer); this.machine.threads.sync.rwlocks[id].writer_queue.push_back(writer);
this.block_thread(writer)
} }
#[inline] #[inline]

View File

@ -32,7 +32,7 @@ pub enum SchedulingAction {
Stop, Stop,
} }
/// Timeout timeout_callbacks can be created by synchronization primitives to tell the /// Timeout callbacks can be created by synchronization primitives to tell the
/// scheduler that they should be called once some period of time passes. /// scheduler that they should be called once some period of time passes.
type TimeoutCallback<'mir, 'tcx> = type TimeoutCallback<'mir, 'tcx> =
Box<dyn FnOnce(&mut InterpCx<'mir, 'tcx, Evaluator<'mir, 'tcx>>) -> InterpResult<'tcx> + 'tcx>; Box<dyn FnOnce(&mut InterpCx<'mir, 'tcx, Evaluator<'mir, 'tcx>>) -> InterpResult<'tcx> + 'tcx>;
@ -189,7 +189,7 @@ struct TimeoutCallbackInfo<'mir, 'tcx> {
impl<'mir, 'tcx> std::fmt::Debug for TimeoutCallbackInfo<'mir, 'tcx> { impl<'mir, 'tcx> std::fmt::Debug for TimeoutCallbackInfo<'mir, 'tcx> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "CallBack({:?})", self.call_time) write!(f, "TimeoutCallback({:?})", self.call_time)
} }
} }
@ -394,7 +394,8 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> {
/// Get a callback that is ready to be called. /// Get a callback that is ready to be called.
fn get_ready_callback(&mut self) -> Option<(ThreadId, TimeoutCallback<'mir, 'tcx>)> { fn get_ready_callback(&mut self) -> Option<(ThreadId, TimeoutCallback<'mir, 'tcx>)> {
// We use a for loop here to make the scheduler more deterministic. // We iterate over all threads in the order of their indices because
// this allows us to have a deterministic scheduler.
for thread in self.threads.indices() { for thread in self.threads.indices() {
match self.timeout_callbacks.entry(thread) { match self.timeout_callbacks.entry(thread) {
Entry::Occupied(entry) => Entry::Occupied(entry) =>

View File

@ -0,0 +1,32 @@
// ignore-windows: No libc on Windows
#![feature(rustc_private)]
extern crate libc;
use std::cell::UnsafeCell;
use std::sync::Arc;
use std::thread;
struct RwLock(UnsafeCell<libc::pthread_rwlock_t>);
unsafe impl Send for RwLock {}
unsafe impl Sync for RwLock {}
fn new_lock() -> Arc<RwLock> {
Arc::new(RwLock(UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER)))
}
fn main() {
unsafe {
let lock = new_lock();
assert_eq!(libc::pthread_rwlock_rdlock(lock.0.get() as *mut _), 0);
let lock_copy = lock.clone();
thread::spawn(move || {
assert_eq!(libc::pthread_rwlock_unlock(lock_copy.0.get() as *mut _), 0); //~ ERROR: Undefined Behavior: unlocked an rwlock that was not locked by the active thread
})
.join()
.unwrap();
}
}

View File

@ -0,0 +1,32 @@
// ignore-windows: No libc on Windows
#![feature(rustc_private)]
extern crate libc;
use std::cell::UnsafeCell;
use std::sync::Arc;
use std::thread;
struct RwLock(UnsafeCell<libc::pthread_rwlock_t>);
unsafe impl Send for RwLock {}
unsafe impl Sync for RwLock {}
fn new_lock() -> Arc<RwLock> {
Arc::new(RwLock(UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER)))
}
fn main() {
unsafe {
let lock = new_lock();
assert_eq!(libc::pthread_rwlock_wrlock(lock.0.get() as *mut _), 0);
let lock_copy = lock.clone();
thread::spawn(move || {
assert_eq!(libc::pthread_rwlock_unlock(lock_copy.0.get() as *mut _), 0); //~ ERROR: Undefined Behavior: unlocked an rwlock that was not locked by the active thread
})
.join()
.unwrap();
}
}