From b2689d5254f8a92a620e7c598046fc51bcb82921 Mon Sep 17 00:00:00 2001 From: Mark Drobnak Date: Thu, 24 Feb 2022 21:03:16 -0800 Subject: [PATCH 1/4] Refactor into multiple modules --- src/condvar.rs | 84 +++++ src/lib.rs | 747 +-------------------------------------------- src/misc.rs | 19 ++ src/mutex.rs | 93 ++++++ src/rwlock.rs | 171 +++++++++++ src/thread.rs | 209 +++++++++++++ src/thread/attr.rs | 108 +++++++ src/thread_keys.rs | 73 +++++ 8 files changed, 770 insertions(+), 734 deletions(-) create mode 100644 src/condvar.rs create mode 100644 src/misc.rs create mode 100644 src/mutex.rs create mode 100644 src/rwlock.rs create mode 100644 src/thread.rs create mode 100644 src/thread/attr.rs create mode 100644 src/thread_keys.rs diff --git a/src/condvar.rs b/src/condvar.rs new file mode 100644 index 0000000..92769c0 --- /dev/null +++ b/src/condvar.rs @@ -0,0 +1,84 @@ +//! PThread condition variables implemented using libctru. + +pub fn init() {} + +#[no_mangle] +pub unsafe extern "C" fn pthread_cond_init( + cond: *mut libc::pthread_cond_t, + _attr: *const libc::pthread_condattr_t, +) -> libc::c_int { + ctru_sys::CondVar_Init(cond as _); + + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn pthread_cond_signal(cond: *mut libc::pthread_cond_t) -> libc::c_int { + ctru_sys::CondVar_WakeUp(cond as _, 1); + + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn pthread_cond_broadcast(cond: *mut libc::pthread_cond_t) -> libc::c_int { + ctru_sys::CondVar_WakeUp(cond as _, -1); + + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn pthread_cond_wait( + cond: *mut libc::pthread_cond_t, + lock: *mut libc::pthread_mutex_t, +) -> libc::c_int { + ctru_sys::CondVar_Wait(cond as _, lock as _); + + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn pthread_cond_timedwait( + cond: *mut libc::pthread_cond_t, + lock: *mut libc::pthread_mutex_t, + abstime: *const libc::timespec, +) -> libc::c_int { + // libctru expects a duration, but we have an absolute timestamp. + // Convert to a duration before calling libctru. + + // Get the current time so we can make a duration + let mut now = libc::timeval { + tv_sec: 0, + tv_usec: 0, + }; + let r = libc::gettimeofday(&mut now, std::ptr::null_mut()); + if r != 0 { + return r; + } + + // Calculate the duration + let duration_nsec = (*abstime) + .tv_sec + // Get the difference in seconds + .saturating_sub(now.tv_sec) + // Convert to nanoseconds + .saturating_mul(1_000_000_000) + // Add the difference in nanoseconds + .saturating_add((*abstime).tv_nsec as i64) + .saturating_sub(now.tv_usec as i64 * 1_000) + // Don't go negative + .max(0); + + let r = ctru_sys::CondVar_WaitTimeout(cond as _, lock as _, duration_nsec); + + // CondVar_WaitTimeout returns a boolean which is true (nonzero) if it timed out + if r == 0 { + 0 + } else { + libc::ETIMEDOUT + } +} + +#[no_mangle] +pub unsafe extern "C" fn pthread_cond_destroy(_cond: *mut libc::pthread_cond_t) -> libc::c_int { + 0 +} diff --git a/src/lib.rs b/src/lib.rs index 4dc7591..43d05e5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,743 +3,22 @@ #![allow(non_camel_case_types)] #![allow(clippy::missing_safety_doc)] -use std::ptr; +mod condvar; +mod misc; +mod mutex; +mod rwlock; +mod thread; +mod thread_keys; /// Call this somewhere to force Rust to link this module. /// The call doesn't need to execute, just exist. /// /// See https://github.com/rust-lang/rust/issues/47384 -pub fn init() {} - -// PTHREAD LAYER TO CALL LIBCTRU - -/// The main thread's thread ID. It is "null" because libctru didn't spawn it. -const MAIN_THREAD_ID: libc::pthread_t = 0; - -#[no_mangle] -pub unsafe extern "C" fn pthread_create( - native: *mut libc::pthread_t, - attr: *const libc::pthread_attr_t, - entrypoint: extern "C" fn(_: *mut libc::c_void) -> *mut libc::c_void, - value: *mut libc::c_void, -) -> libc::c_int { - let attr = attr as *const PThreadAttr; - let stack_size = (*attr).stack_size as ctru_sys::size_t; - let priority = (*attr).priority; - let processor_id = (*attr).processor_id; - - extern "C" fn thread_start(main: *mut libc::c_void) { - unsafe { - Box::from_raw(main as *mut Box *mut libc::c_void>)(); - } - } - - // The closure needs a fat pointer (64 bits) to work since it captures a variable and is thus a - // trait object, but *mut void is only 32 bits. We need double indirection to pass along the - // full closure data. - // We make this closure in the first place because threadCreate expects a void return type, but - // entrypoint returns a pointer so the types are incompatible. - let main: *mut Box *mut libc::c_void> = - Box::into_raw(Box::new(Box::new(move || entrypoint(value)))); - - let thread = ctru_sys::threadCreate( - Some(thread_start), - main as *mut libc::c_void, - stack_size, - priority, - processor_id, - false, - ); - - if thread.is_null() { - // There was some error, but libctru doesn't expose the result. - // We assume there was permissions issue (such as too low of a priority). - // We also need to clean up the closure at this time. - drop(Box::from_raw(main)); - return libc::EPERM; - } - - *native = thread as _; - - 0 -} - -#[no_mangle] -pub unsafe extern "C" fn pthread_join( - native: libc::pthread_t, - _value: *mut *mut libc::c_void, -) -> libc::c_int { - if native == MAIN_THREAD_ID { - // This is not a valid thread to join on - return libc::EINVAL; - } - - let result = ctru_sys::threadJoin(native as ctru_sys::Thread, u64::MAX); - if ctru_sys::R_FAILED(result) { - // TODO: improve the error code by checking the result further? - return libc::EINVAL; - } - - ctru_sys::threadFree(native as ctru_sys::Thread); - - 0 -} - -#[no_mangle] -pub unsafe extern "C" fn pthread_detach(thread: libc::pthread_t) -> libc::c_int { - if thread == MAIN_THREAD_ID { - // This is not a valid thread to detach - return libc::EINVAL; - } - - ctru_sys::threadDetach(thread as ctru_sys::Thread); - - 0 -} - -#[no_mangle] -pub unsafe extern "C" fn pthread_self() -> libc::pthread_t { - ctru_sys::threadGetCurrent() as libc::pthread_t -} - -unsafe fn get_main_thread_handle() -> Result { - // Unfortunately I don't know of any better way to get the main thread's - // handle, other than via svcGetThreadList and svcOpenThread. - // Experimentally, the main thread ID is always the first in the list. - let mut thread_ids = [0; 1]; - let mut thread_ids_count = 0; - let result = ctru_sys::svcGetThreadList( - &mut thread_ids_count, - thread_ids.as_mut_ptr(), - thread_ids.len() as i32, - ctru_sys::CUR_PROCESS_HANDLE, - ); - if ctru_sys::R_FAILED(result) { - return Err(result); - } - - // Get the main thread's handle - let main_thread_id = thread_ids[0]; - let mut main_thread_handle = 0; - let result = ctru_sys::svcOpenThread( - &mut main_thread_handle, - ctru_sys::CUR_PROCESS_HANDLE, - main_thread_id, - ); - if ctru_sys::R_FAILED(result) { - return Err(result); - } - - Ok(main_thread_handle) -} - -unsafe fn get_thread_handle(native: libc::pthread_t) -> Result { - if native == MAIN_THREAD_ID { - get_main_thread_handle() - } else { - Ok(ctru_sys::threadGetHandle(native as ctru_sys::Thread)) - } -} - -#[no_mangle] -pub unsafe extern "C" fn pthread_getschedparam( - native: libc::pthread_t, - policy: *mut libc::c_int, - param: *mut libc::sched_param, -) -> libc::c_int { - let handle = match get_thread_handle(native) { - Ok(handle) => handle, - Err(_) => return libc::ESRCH, - }; - - if handle == u32::MAX { - // The thread has already finished - return libc::ESRCH; - } - - let mut priority = 0; - let result = ctru_sys::svcGetThreadPriority(&mut priority, handle); - if ctru_sys::R_FAILED(result) { - // Some error occurred. This is the only error defined for this - // function, so return it. Maybe the thread exited while this function - // was exiting? - return libc::ESRCH; - } - - (*param).sched_priority = priority; - - // SCHED_FIFO is closest to how the cooperative app core works, while - // SCHED_RR is closest to how the preemptive sys core works. - // However, we don't have an API to get the current processor ID of a chosen - // thread (only the current), so we just always return SCHED_FIFO. - (*policy) = libc::SCHED_FIFO; - - 0 -} - -#[no_mangle] -pub unsafe extern "C" fn pthread_setschedparam( - native: libc::pthread_t, - policy: libc::c_int, - param: *const libc::sched_param, -) -> libc::c_int { - if policy != libc::SCHED_FIFO { - // We only accept SCHED_FIFO. See the note in pthread_getschedparam. - return libc::EINVAL; - } - - let handle = match get_thread_handle(native) { - Ok(handle) => handle, - Err(_) => return libc::EINVAL, - }; - - if handle == u32::MAX { - // The thread has already finished - return libc::ESRCH; - } - - let result = ctru_sys::svcSetThreadPriority(handle, (*param).sched_priority); - if ctru_sys::R_FAILED(result) { - // Probably the priority is out of the permissible bounds - // TODO: improve the error code by checking the result further? - return libc::EPERM; - } - - 0 -} - -#[no_mangle] -pub unsafe extern "C" fn pthread_getprocessorid_np() -> libc::c_int { - ctru_sys::svcGetProcessorID() -} - -/// Internal struct for storing pthread attribute data -/// Must be less than or equal to the size of `libc::pthread_attr_t`. We assert -/// this below via static_assertions. -struct PThreadAttr { - stack_size: libc::size_t, - priority: libc::c_int, - processor_id: libc::c_int, -} - -impl Default for PThreadAttr { - fn default() -> Self { - // Note: we are ignoring the result here, but errors shouldn't occur - // since we're using a valid handle. - let mut priority = 0; - unsafe { ctru_sys::svcGetThreadPriority(&mut priority, ctru_sys::CUR_THREAD_HANDLE) }; - - PThreadAttr { - stack_size: libc::PTHREAD_STACK_MIN, - - // If no priority value is specified, spawn with the same priority - // as the current thread - priority, - - // If no processor is specified, spawn on the default core. - // (determined by the application's Exheader) - processor_id: -2, - } - } -} - -static_assertions::const_assert!( - std::mem::size_of::() <= std::mem::size_of::() -); - -#[no_mangle] -pub unsafe extern "C" fn pthread_attr_init(attr: *mut libc::pthread_attr_t) -> libc::c_int { - let attr = attr as *mut PThreadAttr; - *attr = PThreadAttr::default(); - - 0 -} - -#[no_mangle] -pub unsafe extern "C" fn pthread_attr_destroy(attr: *mut libc::pthread_attr_t) -> libc::c_int { - ptr::drop_in_place(attr as *mut PThreadAttr); - 0 -} - -#[no_mangle] -pub unsafe extern "C" fn pthread_attr_setstacksize( - attr: *mut libc::pthread_attr_t, - stack_size: libc::size_t, -) -> libc::c_int { - let attr = attr as *mut PThreadAttr; - (*attr).stack_size = stack_size; - - 0 -} - -#[no_mangle] -pub unsafe extern "C" fn pthread_attr_getschedparam( - attr: *const libc::pthread_attr_t, - param: *mut libc::sched_param, -) -> libc::c_int { - let attr = attr as *const PThreadAttr; - (*param).sched_priority = (*attr).priority; - 0 -} - -#[no_mangle] -pub unsafe extern "C" fn pthread_attr_setschedparam( - attr: *mut libc::pthread_attr_t, - param: *const libc::sched_param, -) -> libc::c_int { - let attr = attr as *mut PThreadAttr; - (*attr).priority = (*param).sched_priority; - - // TODO: we could validate the priority here if we wanted? - - 0 -} - -#[no_mangle] -pub unsafe extern "C" fn pthread_attr_getprocessorid_np( - attr: *const libc::pthread_attr_t, - processor_id: *mut libc::c_int, -) -> libc::c_int { - let attr = attr as *mut PThreadAttr; - (*processor_id) = (*attr).processor_id; - - 0 -} - -#[no_mangle] -pub unsafe extern "C" fn pthread_attr_setprocessorid_np( - attr: *mut libc::pthread_attr_t, - processor_id: libc::c_int, -) -> libc::c_int { - let attr = attr as *mut PThreadAttr; - (*attr).processor_id = processor_id; - - // TODO: we could validate the processor ID here if we wanted? - - 0 -} - -#[no_mangle] -pub unsafe extern "C" fn sched_yield() -> libc::c_int { - ctru_sys::svcSleepThread(0); - - 0 -} - -#[no_mangle] -pub unsafe extern "C" fn pthread_cond_init( - cond: *mut libc::pthread_cond_t, - _attr: *const libc::pthread_condattr_t, -) -> libc::c_int { - ctru_sys::CondVar_Init(cond as _); - - 0 -} - -#[no_mangle] -pub unsafe extern "C" fn pthread_cond_signal(cond: *mut libc::pthread_cond_t) -> libc::c_int { - ctru_sys::CondVar_WakeUp(cond as _, 1); - - 0 -} - -#[no_mangle] -pub unsafe extern "C" fn pthread_cond_broadcast(cond: *mut libc::pthread_cond_t) -> libc::c_int { - ctru_sys::CondVar_WakeUp(cond as _, -1); - - 0 -} - -#[no_mangle] -pub unsafe extern "C" fn pthread_cond_wait( - cond: *mut libc::pthread_cond_t, - lock: *mut libc::pthread_mutex_t, -) -> libc::c_int { - ctru_sys::CondVar_Wait(cond as _, lock as _); - - 0 -} - -#[no_mangle] -pub unsafe extern "C" fn pthread_cond_timedwait( - cond: *mut libc::pthread_cond_t, - lock: *mut libc::pthread_mutex_t, - abstime: *const libc::timespec, -) -> libc::c_int { - // libctru expects a duration, but we have an absolute timestamp. - // Convert to a duration before calling libctru. - - // Get the current time so we can make a duration - let mut now = libc::timeval { - tv_sec: 0, - tv_usec: 0, - }; - let r = libc::gettimeofday(&mut now, ptr::null_mut()); - if r != 0 { - return r; - } - - // Calculate the duration - let duration_nsec = (*abstime) - .tv_sec - // Get the difference in seconds - .saturating_sub(now.tv_sec) - // Convert to nanoseconds - .saturating_mul(1_000_000_000) - // Add the difference in nanoseconds - .saturating_add((*abstime).tv_nsec as i64) - .saturating_sub(now.tv_usec as i64 * 1_000) - // Don't go negative - .max(0); - - let r = ctru_sys::CondVar_WaitTimeout(cond as _, lock as _, duration_nsec); - - // CondVar_WaitTimeout returns a boolean which is true (nonzero) if it timed out - if r == 0 { - 0 - } else { - libc::ETIMEDOUT - } -} - -#[no_mangle] -pub unsafe extern "C" fn pthread_cond_destroy(_cond: *mut libc::pthread_cond_t) -> libc::c_int { - 0 -} - -#[no_mangle] -pub unsafe extern "C" fn pthread_mutexattr_init( - _attr: *mut libc::pthread_mutexattr_t, -) -> libc::c_int { - 0 -} - -#[no_mangle] -pub unsafe extern "C" fn pthread_mutexattr_settype( - attr: *mut libc::pthread_mutexattr_t, - _type: libc::c_int, -) -> libc::c_int { - let attr: *mut libc::c_int = attr as _; - - *attr = _type as _; - - 0 -} - -#[no_mangle] -pub unsafe extern "C" fn pthread_mutex_init( - lock: *mut libc::pthread_mutex_t, - attr: *const libc::pthread_mutexattr_t, -) -> libc::c_int { - let lock = lock as *mut u8; - - let attr: libc::c_int = *(attr as *const libc::c_int); - - if attr == libc::PTHREAD_MUTEX_NORMAL { - ctru_sys::LightLock_Init(lock as _); - } else if attr == libc::PTHREAD_MUTEX_RECURSIVE { - ctru_sys::RecursiveLock_Init(lock as _) - } - - *(lock.offset(39)) = attr as u8; - - 0 -} - -#[no_mangle] -pub unsafe extern "C" fn pthread_mutex_destroy(_lock: *mut libc::pthread_mutex_t) -> libc::c_int { - 0 -} - -#[no_mangle] -pub unsafe extern "C" fn pthread_mutex_lock(lock: *mut libc::pthread_mutex_t) -> libc::c_int { - let lock = lock as *const u8; - - if *(lock.offset(39)) as u8 == libc::PTHREAD_MUTEX_NORMAL as u8 { - ctru_sys::LightLock_Lock(lock as _); - } else if *(lock.offset(39)) as u8 == libc::PTHREAD_MUTEX_RECURSIVE as u8 { - ctru_sys::RecursiveLock_Lock(lock as _); - } - - 0 -} - -#[no_mangle] -pub unsafe extern "C" fn pthread_mutex_trylock(lock: *mut libc::pthread_mutex_t) -> libc::c_int { - let lock = lock as *const u8; - - if *(lock.offset(39)) as u8 == libc::PTHREAD_MUTEX_NORMAL as u8 { - return ctru_sys::LightLock_TryLock(lock as _); - } else if *(lock.offset(39)) as u8 == libc::PTHREAD_MUTEX_RECURSIVE as u8 { - return ctru_sys::RecursiveLock_TryLock(lock as _); - } - - -1 -} - -#[no_mangle] -pub unsafe extern "C" fn pthread_mutex_unlock(lock: *mut libc::pthread_mutex_t) -> libc::c_int { - let lock = lock as *const u8; - - if *(lock.offset(39)) as u8 == libc::PTHREAD_MUTEX_NORMAL as u8 { - ctru_sys::LightLock_Unlock(lock as _); - } else if *(lock.offset(39)) as u8 == libc::PTHREAD_MUTEX_RECURSIVE as u8 { - ctru_sys::RecursiveLock_Unlock(lock as _); - } - - 0 -} - -#[no_mangle] -pub unsafe extern "C" fn pthread_mutexattr_destroy( - _attr: *mut libc::pthread_mutexattr_t, -) -> libc::c_int { - 0 -} - -struct rwlock_clear { - mutex: libc::pthread_mutex_t, - cvar: i32, - num_readers: i32, - writer_active: bool, - initialized: bool, -} - -/// Initializes the rwlock internal members if they weren't already -fn init_rwlock(lock: *mut libc::pthread_rwlock_t) { - let lock = lock as *mut rwlock_clear; - - unsafe { - if !(*lock).initialized { - let mut attr = std::mem::MaybeUninit::::uninit(); - pthread_mutexattr_init(attr.as_mut_ptr()); - let mut attr = attr.assume_init(); - pthread_mutexattr_settype(&mut attr, libc::PTHREAD_MUTEX_NORMAL); - - pthread_mutex_init(&mut (*lock).mutex, &attr); - pthread_cond_init(&mut (*lock).cvar as *mut i32 as *mut _, core::ptr::null()); - - (*lock).num_readers = 0; - (*lock).writer_active = false; - - (*lock).initialized = true; - } - } -} - -#[no_mangle] -pub unsafe extern "C" fn pthread_rwlock_init( - lock: *mut libc::pthread_rwlock_t, - _attr: *const libc::pthread_rwlockattr_t, -) -> libc::c_int { - init_rwlock(lock); - - 0 -} - -#[no_mangle] -pub unsafe extern "C" fn pthread_rwlock_destroy(_lock: *mut libc::pthread_rwlock_t) -> libc::c_int { - 0 -} - -#[no_mangle] -pub unsafe extern "C" fn pthread_rwlock_rdlock(lock: *mut libc::pthread_rwlock_t) -> libc::c_int { - init_rwlock(lock); - let lock = lock as *mut rwlock_clear; - - pthread_mutex_lock(&mut (*lock).mutex); - - while (*lock).writer_active { - pthread_cond_wait(&mut (*lock).cvar as *mut i32 as _, &mut (*lock).mutex); - } - - (*lock).num_readers += 1; - - pthread_mutex_unlock(&mut (*lock).mutex); - - 0 -} - -#[no_mangle] -pub unsafe extern "C" fn pthread_rwlock_tryrdlock( - lock: *mut libc::pthread_rwlock_t, -) -> libc::c_int { - init_rwlock(lock); - let lock = lock as *mut rwlock_clear; - - if pthread_mutex_trylock(&mut (*lock).mutex) != 0 { - return -1; - } - - while (*lock).writer_active { - pthread_cond_wait(&mut (*lock).cvar as *mut i32 as _, &mut (*lock).mutex); - } - - (*lock).num_readers += 1; - - pthread_mutex_unlock(&mut (*lock).mutex); - - 0 -} - -#[no_mangle] -pub unsafe extern "C" fn pthread_rwlock_wrlock(lock: *mut libc::pthread_rwlock_t) -> libc::c_int { - init_rwlock(lock); - let lock = lock as *mut rwlock_clear; - - pthread_mutex_lock(&mut (*lock).mutex); - - while (*lock).writer_active || (*lock).num_readers > 0 { - pthread_cond_wait(&mut (*lock).cvar as *mut i32 as _, &mut (*lock).mutex); - } - - (*lock).writer_active = true; - - pthread_mutex_unlock(&mut (*lock).mutex); - - 0 -} - -#[no_mangle] -pub unsafe extern "C" fn pthread_rwlock_trywrlock( - lock: *mut libc::pthread_rwlock_t, -) -> libc::c_int { - init_rwlock(lock); - let lock = lock as *mut rwlock_clear; - - if pthread_mutex_trylock(&mut (*lock).mutex) != 0 { - return -1; - } - - while (*lock).writer_active || (*lock).num_readers > 0 { - pthread_cond_wait(&mut (*lock).cvar as *mut i32 as _, &mut (*lock).mutex); - } - - (*lock).writer_active = true; - - pthread_mutex_unlock(&mut (*lock).mutex); - - 0 -} - -#[no_mangle] -pub unsafe extern "C" fn pthread_rwlock_unlock(lock: *mut libc::pthread_rwlock_t) -> libc::c_int { - init_rwlock(lock); - let lock = lock as *mut rwlock_clear; - - pthread_mutex_lock(&mut (*lock).mutex); - - // If there are readers and no writer => Must be a reader - if (*lock).num_readers > 0 && !(*lock).writer_active { - (*lock).num_readers -= 1; - - // If there are no more readers, signal to a waiting writer - if (*lock).num_readers == 0 { - pthread_cond_signal(&mut (*lock).cvar as *mut i32 as _); - } - // If there are no readers and a writer => Must be a writer - } else if (*lock).num_readers == 0 && (*lock).writer_active { - (*lock).writer_active = false; - - pthread_cond_broadcast(&mut (*lock).cvar as *mut i32 as _); - } - - pthread_mutex_unlock(&mut (*lock).mutex); - - 0 -} - -#[no_mangle] -pub unsafe extern "C" fn pthread_rwlockattr_init( - _attr: *mut libc::pthread_rwlockattr_t, -) -> libc::c_int { - 0 -} - -#[no_mangle] -pub unsafe extern "C" fn pthread_rwlockattr_destroy( - _attr: *mut libc::pthread_rwlockattr_t, -) -> libc::c_int { - 0 -} - -#[no_mangle] -pub unsafe extern "C" fn pthread_sigmask( - _how: ::libc::c_int, - _set: *const libc::sigset_t, - _oldset: *mut libc::sigset_t, -) -> ::libc::c_int { - -1 -} - -// THREAD KEYS IMPLEMENTATION FOR RUST STD - -use spin::rwlock::RwLock; -use std::collections::BTreeMap; -use std::sync::atomic::{AtomicUsize, Ordering}; - -type Key = usize; -type Destructor = unsafe extern "C" fn(*mut libc::c_void); - -static NEXT_KEY: AtomicUsize = AtomicUsize::new(1); - -// This is a spin-lock RwLock which yields the thread every loop -static KEYS: RwLock>, spin::Yield> = RwLock::new(BTreeMap::new()); - -#[thread_local] -static mut LOCALS: BTreeMap = BTreeMap::new(); - -fn is_valid_key(key: Key) -> bool { - KEYS.read().contains_key(&(key as Key)) -} - -#[no_mangle] -pub unsafe extern "C" fn pthread_key_create( - key: *mut libc::pthread_key_t, - destructor: Option, -) -> libc::c_int { - let new_key = NEXT_KEY.fetch_add(1, Ordering::SeqCst); - KEYS.write().insert(new_key, destructor); - - *key = new_key as libc::pthread_key_t; - - 0 -} - -#[no_mangle] -pub unsafe extern "C" fn pthread_key_delete(key: libc::pthread_key_t) -> libc::c_int { - match KEYS.write().remove(&(key as Key)) { - // We had a entry, so it was a valid key. - // It's officially undefined behavior if they use the key after this, - // so don't worry about cleaning up LOCALS, especially since we can't - // clean up every thread's map. - Some(_) => 0, - // The key is unknown - None => libc::EINVAL, - } -} - -#[no_mangle] -pub unsafe extern "C" fn pthread_getspecific(key: libc::pthread_key_t) -> *mut libc::c_void { - if let Some(&value) = LOCALS.get(&(key as Key)) { - value as _ - } else { - // Note: we don't care if the key is invalid, we still return null - ptr::null_mut() - } -} - -#[no_mangle] -pub unsafe extern "C" fn pthread_setspecific( - key: libc::pthread_key_t, - value: *const libc::c_void, -) -> libc::c_int { - let key = key as Key; - - if !is_valid_key(key) { - return libc::EINVAL; - } - - LOCALS.insert(key, value as *mut _); - 0 +pub fn init() { + condvar::init(); + misc::init(); + mutex::init(); + rwlock::init(); + thread::init(); + thread_keys::init(); } diff --git a/src/misc.rs b/src/misc.rs new file mode 100644 index 0000000..4aa5182 --- /dev/null +++ b/src/misc.rs @@ -0,0 +1,19 @@ +//! Miscellaneous pthread functions + +pub fn init() {} + +#[no_mangle] +pub unsafe extern "C" fn sched_yield() -> libc::c_int { + ctru_sys::svcSleepThread(0); + + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn pthread_sigmask( + _how: ::libc::c_int, + _set: *const libc::sigset_t, + _oldset: *mut libc::sigset_t, +) -> ::libc::c_int { + -1 +} diff --git a/src/mutex.rs b/src/mutex.rs new file mode 100644 index 0000000..2d8dc41 --- /dev/null +++ b/src/mutex.rs @@ -0,0 +1,93 @@ +//! PThread mutex implemented using libctru. + +pub fn init() {} + +#[no_mangle] +pub unsafe extern "C" fn pthread_mutexattr_init( + _attr: *mut libc::pthread_mutexattr_t, +) -> libc::c_int { + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn pthread_mutexattr_settype( + attr: *mut libc::pthread_mutexattr_t, + _type: libc::c_int, +) -> libc::c_int { + let attr: *mut libc::c_int = attr as _; + + *attr = _type as _; + + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn pthread_mutex_init( + lock: *mut libc::pthread_mutex_t, + attr: *const libc::pthread_mutexattr_t, +) -> libc::c_int { + let lock = lock as *mut u8; + + let attr: libc::c_int = *(attr as *const libc::c_int); + + if attr == libc::PTHREAD_MUTEX_NORMAL { + ctru_sys::LightLock_Init(lock as _); + } else if attr == libc::PTHREAD_MUTEX_RECURSIVE { + ctru_sys::RecursiveLock_Init(lock as _) + } + + *(lock.offset(39)) = attr as u8; + + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn pthread_mutex_destroy(_lock: *mut libc::pthread_mutex_t) -> libc::c_int { + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn pthread_mutex_lock(lock: *mut libc::pthread_mutex_t) -> libc::c_int { + let lock = lock as *const u8; + + if *(lock.offset(39)) as u8 == libc::PTHREAD_MUTEX_NORMAL as u8 { + ctru_sys::LightLock_Lock(lock as _); + } else if *(lock.offset(39)) as u8 == libc::PTHREAD_MUTEX_RECURSIVE as u8 { + ctru_sys::RecursiveLock_Lock(lock as _); + } + + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn pthread_mutex_trylock(lock: *mut libc::pthread_mutex_t) -> libc::c_int { + let lock = lock as *const u8; + + if *(lock.offset(39)) as u8 == libc::PTHREAD_MUTEX_NORMAL as u8 { + return ctru_sys::LightLock_TryLock(lock as _); + } else if *(lock.offset(39)) as u8 == libc::PTHREAD_MUTEX_RECURSIVE as u8 { + return ctru_sys::RecursiveLock_TryLock(lock as _); + } + + -1 +} + +#[no_mangle] +pub unsafe extern "C" fn pthread_mutex_unlock(lock: *mut libc::pthread_mutex_t) -> libc::c_int { + let lock = lock as *const u8; + + if *(lock.offset(39)) as u8 == libc::PTHREAD_MUTEX_NORMAL as u8 { + ctru_sys::LightLock_Unlock(lock as _); + } else if *(lock.offset(39)) as u8 == libc::PTHREAD_MUTEX_RECURSIVE as u8 { + ctru_sys::RecursiveLock_Unlock(lock as _); + } + + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn pthread_mutexattr_destroy( + _attr: *mut libc::pthread_mutexattr_t, +) -> libc::c_int { + 0 +} diff --git a/src/rwlock.rs b/src/rwlock.rs new file mode 100644 index 0000000..e3fc48a --- /dev/null +++ b/src/rwlock.rs @@ -0,0 +1,171 @@ +//! PThread read-write lock implemented using libctru. + +pub fn init() {} + +use crate::{condvar, mutex}; + +struct rwlock_clear { + mutex: libc::pthread_mutex_t, + cvar: i32, + num_readers: i32, + writer_active: bool, + initialized: bool, +} + +/// Initializes the rwlock internal members if they weren't already +fn init_rwlock(lock: *mut libc::pthread_rwlock_t) { + let lock = lock as *mut rwlock_clear; + + unsafe { + if !(*lock).initialized { + let mut attr = std::mem::MaybeUninit::::uninit(); + mutex::pthread_mutexattr_init(attr.as_mut_ptr()); + let mut attr = attr.assume_init(); + mutex::pthread_mutexattr_settype(&mut attr, libc::PTHREAD_MUTEX_NORMAL); + + mutex::pthread_mutex_init(&mut (*lock).mutex, &attr); + condvar::pthread_cond_init(&mut (*lock).cvar as *mut i32 as *mut _, core::ptr::null()); + + (*lock).num_readers = 0; + (*lock).writer_active = false; + + (*lock).initialized = true; + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn pthread_rwlock_init( + lock: *mut libc::pthread_rwlock_t, + _attr: *const libc::pthread_rwlockattr_t, +) -> libc::c_int { + init_rwlock(lock); + + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn pthread_rwlock_destroy(_lock: *mut libc::pthread_rwlock_t) -> libc::c_int { + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn pthread_rwlock_rdlock(lock: *mut libc::pthread_rwlock_t) -> libc::c_int { + init_rwlock(lock); + let lock = lock as *mut rwlock_clear; + + mutex::pthread_mutex_lock(&mut (*lock).mutex); + + while (*lock).writer_active { + condvar::pthread_cond_wait(&mut (*lock).cvar as *mut i32 as _, &mut (*lock).mutex); + } + + (*lock).num_readers += 1; + + mutex::pthread_mutex_unlock(&mut (*lock).mutex); + + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn pthread_rwlock_tryrdlock( + lock: *mut libc::pthread_rwlock_t, +) -> libc::c_int { + init_rwlock(lock); + let lock = lock as *mut rwlock_clear; + + if mutex::pthread_mutex_trylock(&mut (*lock).mutex) != 0 { + return -1; + } + + while (*lock).writer_active { + condvar::pthread_cond_wait(&mut (*lock).cvar as *mut i32 as _, &mut (*lock).mutex); + } + + (*lock).num_readers += 1; + + mutex::pthread_mutex_unlock(&mut (*lock).mutex); + + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn pthread_rwlock_wrlock(lock: *mut libc::pthread_rwlock_t) -> libc::c_int { + init_rwlock(lock); + let lock = lock as *mut rwlock_clear; + + mutex::pthread_mutex_lock(&mut (*lock).mutex); + + while (*lock).writer_active || (*lock).num_readers > 0 { + condvar::pthread_cond_wait(&mut (*lock).cvar as *mut i32 as _, &mut (*lock).mutex); + } + + (*lock).writer_active = true; + + mutex::pthread_mutex_unlock(&mut (*lock).mutex); + + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn pthread_rwlock_trywrlock( + lock: *mut libc::pthread_rwlock_t, +) -> libc::c_int { + init_rwlock(lock); + let lock = lock as *mut rwlock_clear; + + if mutex::pthread_mutex_trylock(&mut (*lock).mutex) != 0 { + return -1; + } + + while (*lock).writer_active || (*lock).num_readers > 0 { + condvar::pthread_cond_wait(&mut (*lock).cvar as *mut i32 as _, &mut (*lock).mutex); + } + + (*lock).writer_active = true; + + mutex::pthread_mutex_unlock(&mut (*lock).mutex); + + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn pthread_rwlock_unlock(lock: *mut libc::pthread_rwlock_t) -> libc::c_int { + init_rwlock(lock); + let lock = lock as *mut rwlock_clear; + + mutex::pthread_mutex_lock(&mut (*lock).mutex); + + // If there are readers and no writer => Must be a reader + if (*lock).num_readers > 0 && !(*lock).writer_active { + (*lock).num_readers -= 1; + + // If there are no more readers, signal to a waiting writer + if (*lock).num_readers == 0 { + condvar::pthread_cond_signal(&mut (*lock).cvar as *mut i32 as _); + } + // If there are no readers and a writer => Must be a writer + } else if (*lock).num_readers == 0 && (*lock).writer_active { + (*lock).writer_active = false; + + condvar::pthread_cond_broadcast(&mut (*lock).cvar as *mut i32 as _); + } + + mutex::pthread_mutex_unlock(&mut (*lock).mutex); + + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn pthread_rwlockattr_init( + _attr: *mut libc::pthread_rwlockattr_t, +) -> libc::c_int { + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn pthread_rwlockattr_destroy( + _attr: *mut libc::pthread_rwlockattr_t, +) -> libc::c_int { + 0 +} diff --git a/src/thread.rs b/src/thread.rs new file mode 100644 index 0000000..d230d5f --- /dev/null +++ b/src/thread.rs @@ -0,0 +1,209 @@ +//! PThread threads implemented using libctru. + +use attr::PThreadAttr; + +mod attr; + +pub fn init() { + attr::init(); +} + +/// The main thread's thread ID. It is "null" because libctru didn't spawn it. +const MAIN_THREAD_ID: libc::pthread_t = 0; + +#[no_mangle] +pub unsafe extern "C" fn pthread_create( + native: *mut libc::pthread_t, + attr: *const libc::pthread_attr_t, + entrypoint: extern "C" fn(_: *mut libc::c_void) -> *mut libc::c_void, + value: *mut libc::c_void, +) -> libc::c_int { + let attr = attr as *const PThreadAttr; + let stack_size = (*attr).stack_size as ctru_sys::size_t; + let priority = (*attr).priority; + let processor_id = (*attr).processor_id; + + extern "C" fn thread_start(main: *mut libc::c_void) { + unsafe { + Box::from_raw(main as *mut Box *mut libc::c_void>)(); + } + } + + // The closure needs a fat pointer (64 bits) to work since it captures a variable and is thus a + // trait object, but *mut void is only 32 bits. We need double indirection to pass along the + // full closure data. + // We make this closure in the first place because threadCreate expects a void return type, but + // entrypoint returns a pointer so the types are incompatible. + let main: *mut Box *mut libc::c_void> = + Box::into_raw(Box::new(Box::new(move || entrypoint(value)))); + + let thread = ctru_sys::threadCreate( + Some(thread_start), + main as *mut libc::c_void, + stack_size, + priority, + processor_id, + false, + ); + + if thread.is_null() { + // There was some error, but libctru doesn't expose the result. + // We assume there was permissions issue (such as too low of a priority). + // We also need to clean up the closure at this time. + drop(Box::from_raw(main)); + return libc::EPERM; + } + + *native = thread as _; + + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn pthread_join( + native: libc::pthread_t, + _value: *mut *mut libc::c_void, +) -> libc::c_int { + if native == MAIN_THREAD_ID { + // This is not a valid thread to join on + return libc::EINVAL; + } + + let result = ctru_sys::threadJoin(native as ctru_sys::Thread, u64::MAX); + if ctru_sys::R_FAILED(result) { + // TODO: improve the error code by checking the result further? + return libc::EINVAL; + } + + ctru_sys::threadFree(native as ctru_sys::Thread); + + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn pthread_detach(thread: libc::pthread_t) -> libc::c_int { + if thread == MAIN_THREAD_ID { + // This is not a valid thread to detach + return libc::EINVAL; + } + + ctru_sys::threadDetach(thread as ctru_sys::Thread); + + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn pthread_self() -> libc::pthread_t { + ctru_sys::threadGetCurrent() as libc::pthread_t +} + +unsafe fn get_main_thread_handle() -> Result { + // Unfortunately I don't know of any better way to get the main thread's + // handle, other than via svcGetThreadList and svcOpenThread. + // Experimentally, the main thread ID is always the first in the list. + let mut thread_ids = [0; 1]; + let mut thread_ids_count = 0; + let result = ctru_sys::svcGetThreadList( + &mut thread_ids_count, + thread_ids.as_mut_ptr(), + thread_ids.len() as i32, + ctru_sys::CUR_PROCESS_HANDLE, + ); + if ctru_sys::R_FAILED(result) { + return Err(result); + } + + // Get the main thread's handle + let main_thread_id = thread_ids[0]; + let mut main_thread_handle = 0; + let result = ctru_sys::svcOpenThread( + &mut main_thread_handle, + ctru_sys::CUR_PROCESS_HANDLE, + main_thread_id, + ); + if ctru_sys::R_FAILED(result) { + return Err(result); + } + + Ok(main_thread_handle) +} + +unsafe fn get_thread_handle(native: libc::pthread_t) -> Result { + if native == MAIN_THREAD_ID { + get_main_thread_handle() + } else { + Ok(ctru_sys::threadGetHandle(native as ctru_sys::Thread)) + } +} + +#[no_mangle] +pub unsafe extern "C" fn pthread_getschedparam( + native: libc::pthread_t, + policy: *mut libc::c_int, + param: *mut libc::sched_param, +) -> libc::c_int { + let handle = match get_thread_handle(native) { + Ok(handle) => handle, + Err(_) => return libc::ESRCH, + }; + + if handle == u32::MAX { + // The thread has already finished + return libc::ESRCH; + } + + let mut priority = 0; + let result = ctru_sys::svcGetThreadPriority(&mut priority, handle); + if ctru_sys::R_FAILED(result) { + // Some error occurred. This is the only error defined for this + // function, so return it. Maybe the thread exited while this function + // was exiting? + return libc::ESRCH; + } + + (*param).sched_priority = priority; + + // SCHED_FIFO is closest to how the cooperative app core works, while + // SCHED_RR is closest to how the preemptive sys core works. + // However, we don't have an API to get the current processor ID of a chosen + // thread (only the current), so we just always return SCHED_FIFO. + (*policy) = libc::SCHED_FIFO; + + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn pthread_setschedparam( + native: libc::pthread_t, + policy: libc::c_int, + param: *const libc::sched_param, +) -> libc::c_int { + if policy != libc::SCHED_FIFO { + // We only accept SCHED_FIFO. See the note in pthread_getschedparam. + return libc::EINVAL; + } + + let handle = match get_thread_handle(native) { + Ok(handle) => handle, + Err(_) => return libc::EINVAL, + }; + + if handle == u32::MAX { + // The thread has already finished + return libc::ESRCH; + } + + let result = ctru_sys::svcSetThreadPriority(handle, (*param).sched_priority); + if ctru_sys::R_FAILED(result) { + // Probably the priority is out of the permissible bounds + // TODO: improve the error code by checking the result further? + return libc::EPERM; + } + + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn pthread_getprocessorid_np() -> libc::c_int { + ctru_sys::svcGetProcessorID() +} diff --git a/src/thread/attr.rs b/src/thread/attr.rs new file mode 100644 index 0000000..039283c --- /dev/null +++ b/src/thread/attr.rs @@ -0,0 +1,108 @@ +use static_assertions::const_assert; +use std::mem; + +pub fn init() {} + +/// Internal struct for storing pthread attribute data +/// Must be less than or equal to the size of `libc::pthread_attr_t`. We assert +/// this below via static_assertions. +pub struct PThreadAttr { + pub(crate) stack_size: libc::size_t, + pub(crate) priority: libc::c_int, + pub(crate) processor_id: libc::c_int, +} + +const_assert!(mem::size_of::() <= mem::size_of::()); + +impl Default for PThreadAttr { + fn default() -> Self { + // Note: we are ignoring the result here, but errors shouldn't occur + // since we're using a valid handle. + let mut priority = 0; + unsafe { ctru_sys::svcGetThreadPriority(&mut priority, ctru_sys::CUR_THREAD_HANDLE) }; + + PThreadAttr { + stack_size: libc::PTHREAD_STACK_MIN, + + // If no priority value is specified, spawn with the same priority + // as the current thread + priority, + + // If no processor is specified, spawn on the default core. + // (determined by the application's Exheader) + processor_id: -2, + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn pthread_attr_init(attr: *mut libc::pthread_attr_t) -> libc::c_int { + let attr = attr as *mut PThreadAttr; + *attr = PThreadAttr::default(); + + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn pthread_attr_destroy(attr: *mut libc::pthread_attr_t) -> libc::c_int { + std::ptr::drop_in_place(attr as *mut PThreadAttr); + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn pthread_attr_setstacksize( + attr: *mut libc::pthread_attr_t, + stack_size: libc::size_t, +) -> libc::c_int { + let attr = attr as *mut PThreadAttr; + (*attr).stack_size = stack_size; + + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn pthread_attr_getschedparam( + attr: *const libc::pthread_attr_t, + param: *mut libc::sched_param, +) -> libc::c_int { + let attr = attr as *const PThreadAttr; + (*param).sched_priority = (*attr).priority; + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn pthread_attr_setschedparam( + attr: *mut libc::pthread_attr_t, + param: *const libc::sched_param, +) -> libc::c_int { + let attr = attr as *mut PThreadAttr; + (*attr).priority = (*param).sched_priority; + + // TODO: we could validate the priority here if we wanted? + + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn pthread_attr_getprocessorid_np( + attr: *const libc::pthread_attr_t, + processor_id: *mut libc::c_int, +) -> libc::c_int { + let attr = attr as *mut PThreadAttr; + (*processor_id) = (*attr).processor_id; + + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn pthread_attr_setprocessorid_np( + attr: *mut libc::pthread_attr_t, + processor_id: libc::c_int, +) -> libc::c_int { + let attr = attr as *mut PThreadAttr; + (*attr).processor_id = processor_id; + + // TODO: we could validate the processor ID here if we wanted? + + 0 +} diff --git a/src/thread_keys.rs b/src/thread_keys.rs new file mode 100644 index 0000000..1deb9d1 --- /dev/null +++ b/src/thread_keys.rs @@ -0,0 +1,73 @@ +//! Thread keys implementation for the standard library. + +use spin::rwlock::RwLock; +use std::collections::BTreeMap; +use std::sync::atomic::{AtomicUsize, Ordering}; + +pub fn init() {} + +type Key = usize; +type Destructor = unsafe extern "C" fn(*mut libc::c_void); + +static NEXT_KEY: AtomicUsize = AtomicUsize::new(1); + +// This is a spin-lock RwLock which yields the thread every loop +static KEYS: RwLock>, spin::Yield> = RwLock::new(BTreeMap::new()); + +#[thread_local] +static mut LOCALS: BTreeMap = BTreeMap::new(); + +fn is_valid_key(key: Key) -> bool { + KEYS.read().contains_key(&(key as Key)) +} + +#[no_mangle] +pub unsafe extern "C" fn pthread_key_create( + key: *mut libc::pthread_key_t, + destructor: Option, +) -> libc::c_int { + let new_key = NEXT_KEY.fetch_add(1, Ordering::SeqCst); + KEYS.write().insert(new_key, destructor); + + *key = new_key as libc::pthread_key_t; + + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn pthread_key_delete(key: libc::pthread_key_t) -> libc::c_int { + match KEYS.write().remove(&(key as Key)) { + // We had a entry, so it was a valid key. + // It's officially undefined behavior if they use the key after this, + // so don't worry about cleaning up LOCALS, especially since we can't + // clean up every thread's map. + Some(_) => 0, + // The key is unknown + None => libc::EINVAL, + } +} + +#[no_mangle] +pub unsafe extern "C" fn pthread_getspecific(key: libc::pthread_key_t) -> *mut libc::c_void { + if let Some(&value) = LOCALS.get(&(key as Key)) { + value as _ + } else { + // Note: we don't care if the key is invalid, we still return null + std::ptr::null_mut() + } +} + +#[no_mangle] +pub unsafe extern "C" fn pthread_setspecific( + key: libc::pthread_key_t, + value: *const libc::c_void, +) -> libc::c_int { + let key = key as Key; + + if !is_valid_key(key) { + return libc::EINVAL; + } + + LOCALS.insert(key, value as *mut _); + 0 +} From f95f4554a108e0108ed3a53686f17ee5888e88a6 Mon Sep 17 00:00:00 2001 From: Mark Drobnak Date: Mon, 28 Feb 2022 21:16:45 -0800 Subject: [PATCH 2/4] Refactor thread tracking to avoid using svcGetThreadList We need to track the threads ourselves now, but that also lets us act more intelligently. We know when the thread is detached, so we can return an error in join before calling libctru. We can also implement the "retval" functionality of `pthread_join`. We can't just only keep track of the thread ID, because libctru expects us to use their `Thread` pointer/data structure. --- src/lib.rs | 1 + src/thread.rs | 263 ++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 200 insertions(+), 64 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 43d05e5..6d30dc5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ #![feature(thread_local)] #![feature(const_btree_new)] +#![feature(let_else)] #![allow(non_camel_case_types)] #![allow(clippy::missing_safety_doc)] diff --git a/src/thread.rs b/src/thread.rs index d230d5f..2e14ce5 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -1,6 +1,10 @@ //! PThread threads implemented using libctru. use attr::PThreadAttr; +use spin::rwlock::RwLock; +use std::collections::BTreeMap; +use std::ptr; +use std::sync::atomic::{AtomicUsize, Ordering}; mod attr; @@ -8,9 +12,38 @@ pub fn init() { attr::init(); } -/// The main thread's thread ID. It is "null" because libctru didn't spawn it. +/// The main thread's pthread ID const MAIN_THREAD_ID: libc::pthread_t = 0; +// We initialize to 1 since 0 is reserved for the main thread. +static NEXT_ID: AtomicUsize = AtomicUsize::new(1); + +// This is a spin-lock RwLock which yields the thread every loop. +static THREADS: RwLock, spin::Yield> = + RwLock::new(BTreeMap::new()); + +// Initialize to zero (main thread ID) since the main thread will have the +// default value in this thread local. +#[thread_local] +static mut THREAD_ID: libc::pthread_t = MAIN_THREAD_ID; + +#[derive(Copy, Clone)] +struct PThread { + thread: SendableThreadPtr, + os_thread_id: u32, + is_detached: bool, + is_finished: bool, + result: *mut libc::c_void, +} + +/// Pointers are not Send (though it's really just a lint). But we want to share +/// the `ctru_sys::Thread` pointer types in the global THREADS map. This struct +/// lets us ignore that "lint". +#[derive(Copy, Clone)] +struct SendableThreadPtr(ctru_sys::Thread); +unsafe impl Send for SendableThreadPtr {} +unsafe impl Sync for SendableThreadPtr {} + #[no_mangle] pub unsafe extern "C" fn pthread_create( native: *mut libc::pthread_t, @@ -23,9 +56,11 @@ pub unsafe extern "C" fn pthread_create( let priority = (*attr).priority; let processor_id = (*attr).processor_id; + let thread_id = NEXT_ID.fetch_add(1, Ordering::SeqCst) as libc::pthread_t; + extern "C" fn thread_start(main: *mut libc::c_void) { unsafe { - Box::from_raw(main as *mut Box *mut libc::c_void>)(); + Box::from_raw(main as *mut Box)(); } } @@ -34,8 +69,28 @@ pub unsafe extern "C" fn pthread_create( // full closure data. // We make this closure in the first place because threadCreate expects a void return type, but // entrypoint returns a pointer so the types are incompatible. - let main: *mut Box *mut libc::c_void> = - Box::into_raw(Box::new(Box::new(move || entrypoint(value)))); + let main: *mut Box = Box::into_raw(Box::new(Box::new(move || { + THREAD_ID = thread_id; + + let result = entrypoint(value); + + // Update the threads map with the result, and remove this thread if + // it's detached. + // We hold the lock the whole time so there isn't a race condition. + // (ex. we copy out the thread data, pthread_detach is called, we + // check is_detached and it's still false, so thread is never + // removed from the map) + let mut thread_map = THREADS.write(); + if let Some(mut pthread) = thread_map.get_mut(&thread_id) { + pthread.is_finished = true; + pthread.result = result; + + if pthread.is_detached { + // libctru will call threadFree once this thread dies + thread_map.remove(&thread_id); + } + } + }))); let thread = ctru_sys::threadCreate( Some(thread_start), @@ -54,106 +109,191 @@ pub unsafe extern "C" fn pthread_create( return libc::EPERM; } - *native = thread as _; + // Get the OS thread ID + let os_handle = ctru_sys::threadGetHandle(thread); + let mut os_thread_id = 0; + let result = ctru_sys::svcGetThreadId(&mut os_thread_id, os_handle); + if ctru_sys::R_FAILED(result) { + // TODO: improve error handling? Different codes? + return libc::EPERM; + } + + // Insert the thread into our map + THREADS.write().insert( + thread_id, + PThread { + thread: SendableThreadPtr(thread), + os_thread_id, + is_detached: false, + is_finished: false, + result: ptr::null_mut(), + }, + ); + + *native = thread_id; 0 } #[no_mangle] pub unsafe extern "C" fn pthread_join( - native: libc::pthread_t, - _value: *mut *mut libc::c_void, + thread_id: libc::pthread_t, + return_value: *mut *mut libc::c_void, ) -> libc::c_int { - if native == MAIN_THREAD_ID { + if thread_id == MAIN_THREAD_ID { // This is not a valid thread to join on return libc::EINVAL; } - let result = ctru_sys::threadJoin(native as ctru_sys::Thread, u64::MAX); + let thread_map = THREADS.read(); + let Some(&pthread) = thread_map.get(&thread_id) else { + // This is not a valid thread ID + return libc::ESRCH + }; + // We need to drop our read guard so it doesn't stay locked while joining + // the thread. + drop(thread_map); + + if pthread.is_detached { + // Cannot join on a detached thread + return libc::EINVAL; + } + + let result = ctru_sys::threadJoin(pthread.thread.0, u64::MAX); if ctru_sys::R_FAILED(result) { // TODO: improve the error code by checking the result further? return libc::EINVAL; } - ctru_sys::threadFree(native as ctru_sys::Thread); + ctru_sys::threadFree(pthread.thread.0); + let thread_data = THREADS.write().remove(&thread_id); + + // This should always be Some, but we use an if let just in case. + if let Some(thread_data) = thread_data { + if !return_value.is_null() { + *return_value = thread_data.result; + } + } 0 } #[no_mangle] -pub unsafe extern "C" fn pthread_detach(thread: libc::pthread_t) -> libc::c_int { - if thread == MAIN_THREAD_ID { +pub unsafe extern "C" fn pthread_detach(thread_id: libc::pthread_t) -> libc::c_int { + if thread_id == MAIN_THREAD_ID { // This is not a valid thread to detach return libc::EINVAL; } - ctru_sys::threadDetach(thread as ctru_sys::Thread); + let mut thread_map = THREADS.write(); + let Some(mut pthread) = thread_map.get_mut(&thread_id) else { + // This is not a valid thread ID + return libc::ESRCH + }; + + if pthread.is_detached { + // Cannot detach an already detached thread + return libc::EINVAL; + } + + pthread.is_detached = true; + ctru_sys::threadDetach(pthread.thread.0); + + if pthread.is_finished { + // threadFree was already called by threadDetach + thread_map.remove(&thread_id); + } 0 } #[no_mangle] pub unsafe extern "C" fn pthread_self() -> libc::pthread_t { - ctru_sys::threadGetCurrent() as libc::pthread_t + let thread_id = THREAD_ID; + + if thread_id == MAIN_THREAD_ID { + // Take this opportunity to populate the main thread's data in the map. + // They can only "legally" get the main thread's ID by calling this + // function, so this will run before they do anything that needs this. + + // We have to ignore the possible error from svcGetThreadId since + // pthread_self cannot fail... But there shouldn't be an error anyways. + let mut os_thread_id = 0; + ctru_sys::svcGetThreadId(&mut os_thread_id, ctru_sys::CUR_THREAD_HANDLE); + + THREADS.write().insert( + MAIN_THREAD_ID, + PThread { + // This null pointer is safe because we return before ever using + // it (in pthread_join and pthread_detach). + thread: SendableThreadPtr(ptr::null_mut()), + os_thread_id, + is_detached: true, + is_finished: false, + result: ptr::null_mut(), + }, + ); + } + + thread_id } -unsafe fn get_main_thread_handle() -> Result { - // Unfortunately I don't know of any better way to get the main thread's - // handle, other than via svcGetThreadList and svcOpenThread. - // Experimentally, the main thread ID is always the first in the list. - let mut thread_ids = [0; 1]; - let mut thread_ids_count = 0; - let result = ctru_sys::svcGetThreadList( - &mut thread_ids_count, - thread_ids.as_mut_ptr(), - thread_ids.len() as i32, - ctru_sys::CUR_PROCESS_HANDLE, - ); - if ctru_sys::R_FAILED(result) { - return Err(result); - } +/// Closes a kernel handle on drop. +struct Handle(ctru_sys::Handle); - // Get the main thread's handle - let main_thread_id = thread_ids[0]; - let mut main_thread_handle = 0; - let result = ctru_sys::svcOpenThread( - &mut main_thread_handle, - ctru_sys::CUR_PROCESS_HANDLE, - main_thread_id, - ); - if ctru_sys::R_FAILED(result) { - return Err(result); - } +impl TryFrom for Handle { + type Error = libc::c_int; + + fn try_from(thread_id: libc::pthread_t) -> Result { + let Some(&pthread) = THREADS.read().get(&thread_id) else { + // This is not a valid thread ID + return Err(libc::ESRCH) + }; - Ok(main_thread_handle) + if pthread.is_finished { + return Err(libc::ESRCH); + } + + let mut os_handle = 0; + let ret = unsafe { + ctru_sys::svcOpenThread( + &mut os_handle, + ctru_sys::CUR_PROCESS_HANDLE, + pthread.os_thread_id, + ) + }; + + if ctru_sys::R_FAILED(ret) || os_handle == u32::MAX { + // Either there was an error or the thread already finished + Err(libc::ESRCH) + } else { + Ok(Self(os_handle)) + } + } } -unsafe fn get_thread_handle(native: libc::pthread_t) -> Result { - if native == MAIN_THREAD_ID { - get_main_thread_handle() - } else { - Ok(ctru_sys::threadGetHandle(native as ctru_sys::Thread)) +impl Drop for Handle { + fn drop(&mut self) { + unsafe { + // We ignore the error because we can't return it or panic. + ctru_sys::svcCloseHandle(self.0); + } } } #[no_mangle] pub unsafe extern "C" fn pthread_getschedparam( - native: libc::pthread_t, + thread_id: libc::pthread_t, policy: *mut libc::c_int, param: *mut libc::sched_param, ) -> libc::c_int { - let handle = match get_thread_handle(native) { + let handle = match Handle::try_from(thread_id) { Ok(handle) => handle, - Err(_) => return libc::ESRCH, + Err(code) => return code, }; - if handle == u32::MAX { - // The thread has already finished - return libc::ESRCH; - } - let mut priority = 0; - let result = ctru_sys::svcGetThreadPriority(&mut priority, handle); + let result = ctru_sys::svcGetThreadPriority(&mut priority, handle.0); if ctru_sys::R_FAILED(result) { // Some error occurred. This is the only error defined for this // function, so return it. Maybe the thread exited while this function @@ -174,7 +314,7 @@ pub unsafe extern "C" fn pthread_getschedparam( #[no_mangle] pub unsafe extern "C" fn pthread_setschedparam( - native: libc::pthread_t, + thread_id: libc::pthread_t, policy: libc::c_int, param: *const libc::sched_param, ) -> libc::c_int { @@ -183,17 +323,12 @@ pub unsafe extern "C" fn pthread_setschedparam( return libc::EINVAL; } - let handle = match get_thread_handle(native) { + let handle = match Handle::try_from(thread_id) { Ok(handle) => handle, - Err(_) => return libc::EINVAL, + Err(code) => return code, }; - if handle == u32::MAX { - // The thread has already finished - return libc::ESRCH; - } - - let result = ctru_sys::svcSetThreadPriority(handle, (*param).sched_priority); + let result = ctru_sys::svcSetThreadPriority(handle.0, (*param).sched_priority); if ctru_sys::R_FAILED(result) { // Probably the priority is out of the permissible bounds // TODO: improve the error code by checking the result further? From 34b9a1826a45dfd2b433f860d606b1bd7a4ffc41 Mon Sep 17 00:00:00 2001 From: Mark Drobnak Date: Mon, 28 Feb 2022 21:49:10 -0800 Subject: [PATCH 3/4] Implement support for "retval" in pthread_join --- src/thread.rs | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/src/thread.rs b/src/thread.rs index 2e14ce5..9d17a15 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -29,20 +29,26 @@ static mut THREAD_ID: libc::pthread_t = MAIN_THREAD_ID; #[derive(Copy, Clone)] struct PThread { - thread: SendableThreadPtr, + thread: SendPtr, os_thread_id: u32, is_detached: bool, is_finished: bool, - result: *mut libc::c_void, + result: SendPtr, } -/// Pointers are not Send (though it's really just a lint). But we want to share -/// the `ctru_sys::Thread` pointer types in the global THREADS map. This struct -/// lets us ignore that "lint". -#[derive(Copy, Clone)] -struct SendableThreadPtr(ctru_sys::Thread); -unsafe impl Send for SendableThreadPtr {} -unsafe impl Sync for SendableThreadPtr {} +/// Pointers are not Send, though it's really just a lint. This struct lets us +/// ignore that "lint". +struct SendPtr(*mut T); +unsafe impl Send for SendPtr {} +unsafe impl Sync for SendPtr {} + +// We can't use the derives because they add an unnecessary T: Copy/Clone bound. +impl Copy for SendPtr {} +impl Clone for SendPtr { + fn clone(&self) -> Self { + *self + } +} #[no_mangle] pub unsafe extern "C" fn pthread_create( @@ -83,7 +89,7 @@ pub unsafe extern "C" fn pthread_create( let mut thread_map = THREADS.write(); if let Some(mut pthread) = thread_map.get_mut(&thread_id) { pthread.is_finished = true; - pthread.result = result; + pthread.result.0 = result; if pthread.is_detached { // libctru will call threadFree once this thread dies @@ -122,11 +128,11 @@ pub unsafe extern "C" fn pthread_create( THREADS.write().insert( thread_id, PThread { - thread: SendableThreadPtr(thread), + thread: SendPtr(thread), os_thread_id, is_detached: false, is_finished: false, - result: ptr::null_mut(), + result: SendPtr(ptr::null_mut()), }, ); @@ -171,7 +177,7 @@ pub unsafe extern "C" fn pthread_join( // This should always be Some, but we use an if let just in case. if let Some(thread_data) = thread_data { if !return_value.is_null() { - *return_value = thread_data.result; + *return_value = thread_data.result.0; } } @@ -226,11 +232,11 @@ pub unsafe extern "C" fn pthread_self() -> libc::pthread_t { PThread { // This null pointer is safe because we return before ever using // it (in pthread_join and pthread_detach). - thread: SendableThreadPtr(ptr::null_mut()), + thread: SendPtr(ptr::null_mut()), os_thread_id, is_detached: true, is_finished: false, - result: ptr::null_mut(), + result: SendPtr(ptr::null_mut()), }, ); } From b84e4e1ed05ce1f1cce8d71ffc7f330c599f29ee Mon Sep 17 00:00:00 2001 From: Mark Drobnak Date: Mon, 28 Feb 2022 21:51:08 -0800 Subject: [PATCH 4/4] Rename SendPtr to ShareablePtr because it implements Sync as well --- src/thread.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/thread.rs b/src/thread.rs index 9d17a15..c7f4d55 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -29,22 +29,22 @@ static mut THREAD_ID: libc::pthread_t = MAIN_THREAD_ID; #[derive(Copy, Clone)] struct PThread { - thread: SendPtr, + thread: ShareablePtr, os_thread_id: u32, is_detached: bool, is_finished: bool, - result: SendPtr, + result: ShareablePtr, } -/// Pointers are not Send, though it's really just a lint. This struct lets us -/// ignore that "lint". -struct SendPtr(*mut T); -unsafe impl Send for SendPtr {} -unsafe impl Sync for SendPtr {} +/// Pointers are not Send or Sync, though it's really just a lint. This struct +/// lets us ignore that "lint". +struct ShareablePtr(*mut T); +unsafe impl Send for ShareablePtr {} +unsafe impl Sync for ShareablePtr {} // We can't use the derives because they add an unnecessary T: Copy/Clone bound. -impl Copy for SendPtr {} -impl Clone for SendPtr { +impl Copy for ShareablePtr {} +impl Clone for ShareablePtr { fn clone(&self) -> Self { *self } @@ -128,11 +128,11 @@ pub unsafe extern "C" fn pthread_create( THREADS.write().insert( thread_id, PThread { - thread: SendPtr(thread), + thread: ShareablePtr(thread), os_thread_id, is_detached: false, is_finished: false, - result: SendPtr(ptr::null_mut()), + result: ShareablePtr(ptr::null_mut()), }, ); @@ -232,11 +232,11 @@ pub unsafe extern "C" fn pthread_self() -> libc::pthread_t { PThread { // This null pointer is safe because we return before ever using // it (in pthread_join and pthread_detach). - thread: SendPtr(ptr::null_mut()), + thread: ShareablePtr(ptr::null_mut()), os_thread_id, is_detached: true, is_finished: false, - result: SendPtr(ptr::null_mut()), + result: ShareablePtr(ptr::null_mut()), }, ); }