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..6d30dc5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,745 +1,25 @@ #![feature(thread_local)] #![feature(const_btree_new)] +#![feature(let_else)] #![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..c7f4d55 --- /dev/null +++ b/src/thread.rs @@ -0,0 +1,350 @@ +//! 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; + +pub fn init() { + attr::init(); +} + +/// 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: ShareablePtr, + os_thread_id: u32, + is_detached: bool, + is_finished: bool, + result: ShareablePtr, +} + +/// 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 ShareablePtr {} +impl Clone for ShareablePtr { + fn clone(&self) -> Self { + *self + } +} + +#[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; + + 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)(); + } + } + + // 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 = 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.0 = 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), + 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; + } + + // 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: ShareablePtr(thread), + os_thread_id, + is_detached: false, + is_finished: false, + result: ShareablePtr(ptr::null_mut()), + }, + ); + + *native = thread_id; + + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn pthread_join( + thread_id: libc::pthread_t, + return_value: *mut *mut libc::c_void, +) -> libc::c_int { + if thread_id == MAIN_THREAD_ID { + // This is not a valid thread to join on + return libc::EINVAL; + } + + 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(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; + } + } + + 0 +} + +#[no_mangle] +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; + } + + 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 { + 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: ShareablePtr(ptr::null_mut()), + os_thread_id, + is_detached: true, + is_finished: false, + result: ShareablePtr(ptr::null_mut()), + }, + ); + } + + thread_id +} + +/// Closes a kernel handle on drop. +struct Handle(ctru_sys::Handle); + +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) + }; + + 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)) + } + } +} + +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( + thread_id: libc::pthread_t, + policy: *mut libc::c_int, + param: *mut libc::sched_param, +) -> libc::c_int { + let handle = match Handle::try_from(thread_id) { + Ok(handle) => handle, + Err(code) => return code, + }; + + let mut priority = 0; + 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 + // 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( + thread_id: 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 Handle::try_from(thread_id) { + Ok(handle) => handle, + Err(code) => return code, + }; + + 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? + 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 +}