std: use queue-based RwLock on SGX

This commit is contained in:
joboet
2024-04-11 19:36:30 +02:00
parent 72fe8a0f00
commit a30a79c5b4
5 changed files with 47 additions and 261 deletions
@@ -0,0 +1,46 @@
//! The functions in this module are needed by libunwind. These symbols are named
//! in pre-link args for the target specification, so keep that in sync.
#![cfg(not(test))]
use crate::sys::sync::RwLock;
// Verify that the byte pattern libunwind uses to initialize an RwLock is
// equivalent to the value of RwLock::new(). If the value changes,
// `src/UnwindRustSgx.h` in libunwind needs to be changed too.
const _: () = unsafe {
let bits_rust: usize = crate::mem::transmute(RwLock::new());
assert!(bits_rust == 0);
};
const EINVAL: i32 = 22;
#[no_mangle]
pub unsafe extern "C" fn __rust_rwlock_rdlock(p: *mut RwLock) -> i32 {
if p.is_null() {
return EINVAL;
}
// We cannot differentiate between reads an writes in unlock and therefore
// always use a write-lock. Unwinding isn't really in the hot path anyway.
unsafe { (*p).write() };
return 0;
}
#[no_mangle]
pub unsafe extern "C" fn __rust_rwlock_wrlock(p: *mut RwLock) -> i32 {
if p.is_null() {
return EINVAL;
}
unsafe { (*p).write() };
return 0;
}
#[no_mangle]
pub unsafe extern "C" fn __rust_rwlock_unlock(p: *mut RwLock) -> i32 {
if p.is_null() {
return EINVAL;
}
unsafe { (*p).write_unlock() };
return 0;
}
+1
View File
@@ -17,6 +17,7 @@
pub mod fs;
#[path = "../unsupported/io.rs"]
pub mod io;
mod libunwind_integration;
pub mod net;
pub mod os;
#[path = "../unsupported/pipe.rs"]
@@ -52,10 +52,6 @@ pub const fn new(var: T) -> Self {
WaitVariable { queue: WaitQueue::new(), lock: var }
}
pub fn queue_empty(&self) -> bool {
self.queue.is_empty()
}
pub fn lock_var(&self) -> &T {
&self.lock
}
@@ -98,19 +94,6 @@ fn default() -> Self {
}
}
impl<'a, T> WaitGuard<'a, T> {
/// Returns which TCSes will be notified when this guard drops.
pub fn notified_tcs(&self) -> NotifiedTcs {
self.notified_tcs
}
/// Drop this `WaitGuard`, after dropping another `guard`.
pub fn drop_after<U>(self, guard: U) {
drop(guard);
drop(self);
}
}
impl<'a, T> Deref for WaitGuard<'a, T> {
type Target = SpinMutexGuard<'a, WaitVariable<T>>;
@@ -141,10 +124,6 @@ pub const fn new() -> Self {
WaitQueue { inner: UnsafeList::new() }
}
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
/// Adds the calling thread to the `WaitVariable`'s wait queue, then wait
/// until a wakeup event.
///
-219
View File
@@ -1,219 +0,0 @@
#[cfg(test)]
mod tests;
use crate::alloc::Layout;
use crate::num::NonZero;
use crate::sys::pal::waitqueue::{
try_lock_or_false, NotifiedTcs, SpinMutex, SpinMutexGuard, WaitQueue, WaitVariable,
};
use crate::sys_common::lazy_box::{LazyBox, LazyInit};
struct AllocatedRwLock {
readers: SpinMutex<WaitVariable<Option<NonZero<usize>>>>,
writer: SpinMutex<WaitVariable<bool>>,
}
pub struct RwLock {
inner: LazyBox<AllocatedRwLock>,
}
impl LazyInit for AllocatedRwLock {
fn init() -> Box<Self> {
Box::new(AllocatedRwLock {
readers: SpinMutex::new(WaitVariable::new(None)),
writer: SpinMutex::new(WaitVariable::new(false)),
})
}
}
// Check at compile time that RwLock's size and alignment matches the C definition
// in libunwind (see also `test_c_rwlock_initializer` in `tests`).
const _: () = {
let rust = Layout::new::<RwLock>();
let c = Layout::new::<*mut ()>();
assert!(rust.size() == c.size());
assert!(rust.align() == c.align());
};
impl RwLock {
pub const fn new() -> RwLock {
RwLock { inner: LazyBox::new() }
}
#[inline]
pub fn read(&self) {
let lock = &*self.inner;
let mut rguard = lock.readers.lock();
let wguard = lock.writer.lock();
if *wguard.lock_var() || !wguard.queue_empty() {
// Another thread has or is waiting for the write lock, wait
drop(wguard);
WaitQueue::wait(rguard, || {});
// Another thread has passed the lock to us
} else {
// No waiting writers, acquire the read lock
*rguard.lock_var_mut() = NonZero::new(rguard.lock_var().map_or(0, |n| n.get()) + 1);
}
}
#[inline]
pub unsafe fn try_read(&self) -> bool {
let lock = &*self.inner;
let mut rguard = try_lock_or_false!(lock.readers);
let wguard = try_lock_or_false!(lock.writer);
if *wguard.lock_var() || !wguard.queue_empty() {
// Another thread has or is waiting for the write lock
false
} else {
// No waiting writers, acquire the read lock
*rguard.lock_var_mut() = NonZero::new(rguard.lock_var().map_or(0, |n| n.get()) + 1);
true
}
}
#[inline]
pub fn write(&self) {
let lock = &*self.inner;
let rguard = lock.readers.lock();
let mut wguard = lock.writer.lock();
if *wguard.lock_var() || rguard.lock_var().is_some() {
// Another thread has the lock, wait
drop(rguard);
WaitQueue::wait(wguard, || {});
// Another thread has passed the lock to us
} else {
// We are just now obtaining the lock
*wguard.lock_var_mut() = true;
}
}
#[inline]
pub fn try_write(&self) -> bool {
let lock = &*self.inner;
let rguard = try_lock_or_false!(lock.readers);
let mut wguard = try_lock_or_false!(lock.writer);
if *wguard.lock_var() || rguard.lock_var().is_some() {
// Another thread has the lock
false
} else {
// We are just now obtaining the lock
*wguard.lock_var_mut() = true;
true
}
}
#[inline]
unsafe fn __read_unlock(
&self,
mut rguard: SpinMutexGuard<'_, WaitVariable<Option<NonZero<usize>>>>,
wguard: SpinMutexGuard<'_, WaitVariable<bool>>,
) {
*rguard.lock_var_mut() = NonZero::new(rguard.lock_var().unwrap().get() - 1);
if rguard.lock_var().is_some() {
// There are other active readers
} else {
if let Ok(mut wguard) = WaitQueue::notify_one(wguard) {
// A writer was waiting, pass the lock
*wguard.lock_var_mut() = true;
wguard.drop_after(rguard);
} else {
// No writers were waiting, the lock is released
rtassert!(rguard.queue_empty());
}
}
}
#[inline]
pub unsafe fn read_unlock(&self) {
let lock = &*self.inner;
let rguard = lock.readers.lock();
let wguard = lock.writer.lock();
unsafe { self.__read_unlock(rguard, wguard) };
}
#[inline]
unsafe fn __write_unlock(
&self,
rguard: SpinMutexGuard<'_, WaitVariable<Option<NonZero<usize>>>>,
wguard: SpinMutexGuard<'_, WaitVariable<bool>>,
) {
match WaitQueue::notify_one(wguard) {
Err(mut wguard) => {
// No writers waiting, release the write lock
*wguard.lock_var_mut() = false;
if let Ok(mut rguard) = WaitQueue::notify_all(rguard) {
// One or more readers were waiting, pass the lock to them
if let NotifiedTcs::All { count } = rguard.notified_tcs() {
*rguard.lock_var_mut() = Some(count)
} else {
unreachable!() // called notify_all
}
rguard.drop_after(wguard);
} else {
// No readers waiting, the lock is released
}
}
Ok(wguard) => {
// There was a thread waiting for write, just pass the lock
wguard.drop_after(rguard);
}
}
}
#[inline]
pub unsafe fn write_unlock(&self) {
let lock = &*self.inner;
let rguard = lock.readers.lock();
let wguard = lock.writer.lock();
unsafe { self.__write_unlock(rguard, wguard) };
}
// only used by __rust_rwlock_unlock below
#[inline]
#[cfg_attr(test, allow(dead_code))]
unsafe fn unlock(&self) {
let lock = &*self.inner;
let rguard = lock.readers.lock();
let wguard = lock.writer.lock();
if *wguard.lock_var() == true {
unsafe { self.__write_unlock(rguard, wguard) };
} else {
unsafe { self.__read_unlock(rguard, wguard) };
}
}
}
// The following functions are needed by libunwind. These symbols are named
// in pre-link args for the target specification, so keep that in sync.
#[cfg(not(test))]
const EINVAL: i32 = 22;
#[cfg(not(test))]
#[no_mangle]
pub unsafe extern "C" fn __rust_rwlock_rdlock(p: *mut RwLock) -> i32 {
if p.is_null() {
return EINVAL;
}
unsafe { (*p).read() };
return 0;
}
#[cfg(not(test))]
#[no_mangle]
pub unsafe extern "C" fn __rust_rwlock_wrlock(p: *mut RwLock) -> i32 {
if p.is_null() {
return EINVAL;
}
unsafe { (*p).write() };
return 0;
}
#[cfg(not(test))]
#[no_mangle]
pub unsafe extern "C" fn __rust_rwlock_unlock(p: *mut RwLock) -> i32 {
if p.is_null() {
return EINVAL;
}
unsafe { (*p).unlock() };
return 0;
}
@@ -1,21 +0,0 @@
use super::*;
use crate::ptr;
// Verify that the byte pattern libunwind uses to initialize an RwLock is
// equivalent to the value of RwLock::new(). If the value changes,
// `src/UnwindRustSgx.h` in libunwind needs to be changed too.
#[test]
fn test_c_rwlock_initializer() {
const C_RWLOCK_INIT: *mut () = ptr::null_mut();
// For the test to work, we need the padding/unused bytes in RwLock to be
// initialized as 0. In practice, this is the case with statics.
static RUST_RWLOCK_INIT: RwLock = RwLock::new();
unsafe {
// If the assertion fails, that not necessarily an issue with the value
// of C_RWLOCK_INIT. It might just be an issue with the way padding
// bytes are initialized in the test code.
assert_eq!(crate::mem::transmute_copy::<_, *mut ()>(&RUST_RWLOCK_INIT), C_RWLOCK_INIT);
};
}