Files
ppsspp/libretro/libretro-common/rthreads/rthreads.c
T
libretroadmin cf9d102864 libretro: vendor libretro-common from RetroArch in-tree
The libretro-common submodule pinned the standalone repository at
76a3d54. It is replaced by an in-tree copy of RetroArch master
(b655a20) holding only what the core uses: the VFS, file, string,
encoding, compat and time sources it already built, the headers they and
the core include, the threading set - rthreads, retro_atomic, retro_spsc
and retro_waitable_spsc, mpsc_stack, retro_triple_buffer,
retro_eventcount, retro_asym_eventcount, retro_procbarrier and tpool,
with features_cpu and rstrtod, which they need - and the codecs that
replace the bundled zlib, zstd, libchdr, libzip and libpng: rinflate and
rdeflate, rzstd, rchd with its LZMA and FLAC decoders, rzip_archive,
rpng and crc32.

stdstring.c, which nothing calls, is no longer built. The threading sources are always built, by both the static Makefile and
CMake; the VFS set stays behind HAVE_LIBRETRO_VFS as before. The codecs
are built by the change that switches the core over to them.
2026-10-08 14:14:37 +02:00

3914 lines
121 KiB
C

/* Copyright (C) 2010-2020 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (rthreads.c).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifdef __unix__
#ifndef __sun__
#ifndef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 199309
#endif
#endif
#endif
/* Affinity: cpu_set_t, CPU_SET and pthread_setaffinity_np are GNU
* extensions, exposed only when this is set before the first system
* header. */
#if defined(__linux__) && !defined(_GNU_SOURCE)
#define _GNU_SOURCE
#endif
#include <stdlib.h>
#include <string.h>
#include <boolean.h>
#include <rthreads/rthreads.h>
#include <retro_atomic.h>
/* with RETRO_WIN32_USE_PTHREADS, pthreads can be used even on win32.
* Maybe only supported in MSVC>=2005 */
#if defined(_WIN32) && !defined(RETRO_WIN32_USE_PTHREADS)
#define USE_WIN32_THREADS
#ifdef _XBOX
#include <xtl.h>
#else
#define WIN32_LEAN_AND_MEAN
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0500 /*_WIN32_WINNT_WIN2K */
#endif
#include <windows.h>
#endif
#elif defined(GEKKO) && !defined(GEKKO_NATIVE)
#define USE_GX_THREADS
#include <gccore.h>
#include <ogc/lwp.h>
#include <ogc/mutex.h>
#include <ogc/cond.h>
#define STACKSIZE (8 * 1024)
#elif defined(GEKKO_NATIVE)
/* os/gekko's own threads under the pthread names used below, so this
* builds on every devkitPPC: newlib's pthreads only came with r49. */
#define USE_GEKKO_THREADS
#include <errno.h>
#include <gekko/gekko.h>
#include <gekko/thread.h>
#include <retro_inline.h>
typedef gk_thread_t *rthreads_gk_thread_t;
typedef struct
{
size_t stack_size;
} rthreads_gk_attr_t;
static INLINE int rthreads_gk_attr_init(rthreads_gk_attr_t *attr)
{
attr->stack_size = 0;
return 0;
}
static INLINE int rthreads_gk_attr_setstacksize(rthreads_gk_attr_t *attr,
size_t stack_size)
{
attr->stack_size = stack_size;
return 0;
}
static INLINE int rthreads_gk_create(gk_thread_t **t,
const rthreads_gk_attr_t *attr, void *(*fn)(void*), void *arg)
{
*t = gk_thread_create(fn, arg, NULL,
(attr && attr->stack_size) ? attr->stack_size : 64 * 1024,
GK_PRIO_DEFAULT);
return *t ? 0 : EAGAIN;
}
static INLINE int rthreads_gk_join(gk_thread_t *t)
{
gk_thread_join(t);
return 0;
}
static INLINE int rthreads_gk_detach(gk_thread_t *t)
{
gk_thread_detach(t);
return 0;
}
static INLINE int rthreads_gk_mutex_init(gk_mutex_t *m)
{
m->word = 0;
return 0;
}
static INLINE int rthreads_gk_mutex_lock(gk_mutex_t *m)
{
gk_mutex_lock(m);
return 0;
}
static INLINE int rthreads_gk_mutex_unlock(gk_mutex_t *m)
{
gk_mutex_unlock(m);
return 0;
}
static INLINE int rthreads_gk_cond_init(gk_cond_t *c)
{
c->seq = 0;
return 0;
}
static INLINE int rthreads_gk_cond_signal(gk_cond_t *c)
{
gk_cond_signal(c);
return 0;
}
static INLINE int rthreads_gk_cond_broadcast(gk_cond_t *c)
{
gk_cond_broadcast(c);
return 0;
}
#define pthread_t rthreads_gk_thread_t
#define pthread_attr_t rthreads_gk_attr_t
#define pthread_mutex_t gk_mutex_t
#define pthread_cond_t gk_cond_t
#define pthread_attr_init(a) rthreads_gk_attr_init(a)
#define pthread_attr_destroy(a) ((void)(a))
#define pthread_attr_setstacksize(a, n) rthreads_gk_attr_setstacksize(a, n)
#define pthread_create(t, a, fn, arg) rthreads_gk_create(t, a, fn, arg)
#define pthread_join(t, r) rthreads_gk_join(t)
#define pthread_detach(t) rthreads_gk_detach(t)
#define pthread_self() gk_thread_self()
#define pthread_equal(a, b) ((a) == (b))
#define pthread_mutex_init(m, a) rthreads_gk_mutex_init(m)
#define pthread_mutex_destroy(m) ((void)(m))
#define pthread_mutex_lock(m) rthreads_gk_mutex_lock(m)
#define pthread_mutex_trylock(m) gk_mutex_trylock(m)
#define pthread_mutex_unlock(m) rthreads_gk_mutex_unlock(m)
#define pthread_cond_init(c, a) rthreads_gk_cond_init(c)
#define pthread_cond_destroy(c) ((void)(c))
#define pthread_cond_wait(c, m) gk_cond_wait(c, m, GK_WAIT_FOREVER)
#define pthread_cond_signal(c) rthreads_gk_cond_signal(c)
#define pthread_cond_broadcast(c) rthreads_gk_cond_broadcast(c)
#elif defined(_3DS)
#define USE_CTR_THREADS
#include <3ds/thread.h>
#include <3ds/synchronization.h>
#include <3ds/svc.h>
#include <3ds/services/apt.h>
#include <retro_inline.h>
#define STACKSIZE (32 * 1024)
#elif defined(PSP)
#define USE_PSP_THREADS
#include <pspkernel.h>
#include <pspthreadman.h>
#define STACKSIZE (32 * 1024)
#elif defined(WIIU)
#define USE_WIIU_THREADS
#include <malloc.h>
#include <string.h>
#if defined(__has_include)
#if __has_include(<wiiu/os/thread.h>)
#define RTHREADS_WIIU_VENDORED
#endif
#endif
#ifdef RTHREADS_WIIU_VENDORED
#include <wiiu/os/thread.h>
#include <wiiu/os/fastmutex.h>
#include <wiiu/os/event.h>
#include <wiiu/os/time.h>
#include <wiiu/os/systeminfo.h>
#else
#include <coreinit/thread.h>
#include <coreinit/fastmutex.h>
#include <coreinit/event.h>
#include <coreinit/time.h>
#include <coreinit/systeminfo.h>
#endif
#ifndef OSMicroseconds
#define OSMicroseconds(us) OSMicrosecondsToTicks(us)
#endif
#define STACKSIZE (128 * 1024)
#elif defined(PS2)
#define USE_PS2_THREADS
#include <kernel.h>
#include <timer.h>
#include <timer_alarm.h>
#include <malloc.h>
#include <string.h>
#define STACKSIZE (32 * 1024)
#elif defined(__SWITCH__)
#define USE_SWITCH_THREADS
#include <switch.h>
#define STACKSIZE (128 * 1024)
#elif defined(VITA)
#define USE_VITA_THREADS
#include <psp2/kernel/threadmgr.h>
#ifdef DEBUG
#include <retro_assert.h>
#endif
#define STACKSIZE (64 * 1024)
#elif defined(__PS3__)
#define USE_PS3_THREADS
#ifdef __PSL1GHT__
#include <sys/thread.h>
#include <lv2/mutex.h>
#include <lv2/cond.h>
#else
#include <sys/ppu_thread.h>
#include <sys/synchronization.h>
#endif
#ifdef DEBUG
#include <retro_assert.h>
#endif
#define STACKSIZE (128 * 1024)
#else
#include <pthread.h>
#include <time.h>
#if (defined(__FreeBSD__) && !defined(__ORBIS__) && !defined(ORBIS)) \
|| defined(__OpenBSD__) || defined(__DragonFly__)
/* pthread_set_name_np() lives here, not in pthread.h. The PS4 toolchain
* defines __FreeBSD__ but Sony's libc ships no pthread_np.h. */
#include <pthread_np.h>
#endif
#endif
#if defined(USE_CTR_THREADS) && !defined(USE_CTRULIB_2)
/* Backported CondVar API from libctru 2.0, and under its license:
https://github.com/devkitPro/libctru
Slightly modified for compatibility with older libctru. */
typedef s32 CondVar;
static INLINE Result syncArbitrateAddress(s32* addr, ArbitrationType type, s32 value)
{
return svcArbitrateAddress(__sync_get_arbiter(), (u32)addr, type, value, 0);
}
static INLINE Result syncArbitrateAddressWithTimeout(s32* addr, ArbitrationType type, s32 value, s64 timeout_ns)
{
return svcArbitrateAddress(__sync_get_arbiter(), (u32)addr, type, value, timeout_ns);
}
static INLINE void __dmb(void)
{
__asm__ __volatile__("mcr p15, 0, %[val], c7, c10, 5" :: [val] "r" (0) : "memory");
}
static INLINE void CondVar_BeginWait(CondVar* cv, LightLock* lock)
{
s32 val;
do
val = __ldrex(cv) - 1;
while (__strex(cv, val));
LightLock_Unlock(lock);
}
static INLINE bool CondVar_EndWait(CondVar* cv, s32 num_threads)
{
bool hasWaiters;
s32 val;
do {
val = __ldrex(cv);
hasWaiters = val < 0;
if (hasWaiters)
{
if (num_threads < 0)
val = 0;
else if (val <= -num_threads)
val += num_threads;
else
val = 0;
}
} while (__strex(cv, val));
return hasWaiters;
}
static INLINE void CondVar_Init(CondVar* cv)
{
*cv = 0;
}
static INLINE void CondVar_Wait(CondVar* cv, LightLock* lock)
{
CondVar_BeginWait(cv, lock);
syncArbitrateAddress(cv, ARBITRATION_WAIT_IF_LESS_THAN, 0);
LightLock_Lock(lock);
}
static INLINE int CondVar_WaitTimeout(CondVar* cv, LightLock* lock, s64 timeout_ns)
{
CondVar_BeginWait(cv, lock);
bool timedOut = false;
Result rc = syncArbitrateAddressWithTimeout(cv, ARBITRATION_WAIT_IF_LESS_THAN_TIMEOUT, 0, timeout_ns);
if (R_DESCRIPTION(rc) == RD_TIMEOUT)
{
timedOut = CondVar_EndWait(cv, 1);
__dmb();
}
LightLock_Lock(lock);
return timedOut;
}
static INLINE void CondVar_WakeUp(CondVar* cv, s32 num_threads)
{
__dmb();
if (CondVar_EndWait(cv, num_threads))
syncArbitrateAddress(cv, ARBITRATION_SIGNAL, num_threads);
else
__dmb();
}
static INLINE void CondVar_Signal(CondVar* cv)
{
CondVar_WakeUp(cv, 1);
}
static INLINE void CondVar_Broadcast(CondVar* cv)
{
CondVar_WakeUp(cv, ARBITRATION_SIGNAL_ALL);
}
/* End libctru 2.0 backport */
#endif
#ifdef USE_PS3_THREADS
/* Both PS3 SDKs put the same lv2 primitives behind different names, so
* the backend below is written against one set of its own. The names
* are prefixed because a unity build drops this translation unit in
* beside every other one, where the SDK spellings may already mean
* something: nothing here can be what another file sees. */
#ifdef __PSL1GHT__
typedef sys_lwmutex_attr_t rthreads_ps3_lwmutex_attr_t;
typedef sys_lwcond_attr_t rthreads_ps3_lwcond_attr_t;
/* Values, rather than the attribute keys some PSL1GHT vintages spell
* instead; the pair is what a plain non-recursive mutex asks for. */
#ifndef SYS_LWMUTEX_PROTOCOL_PRIO
#define SYS_LWMUTEX_PROTOCOL_PRIO 2
#endif
#ifndef SYS_LWMUTEX_ATTR_NOT_RECURSIVE
#define SYS_LWMUTEX_ATTR_NOT_RECURSIVE 0x0020
#endif
#define RTHREADS_PS3_LWMUTEX_ATTR_INIT(attr) \
do { \
(attr).attr_protocol = SYS_LWMUTEX_PROTOCOL_PRIO; \
(attr).attr_recursive = SYS_LWMUTEX_ATTR_NOT_RECURSIVE; \
(attr).name[0] = '\0'; \
} while (0)
#define RTHREADS_PS3_LWCOND_ATTR_INIT(attr) \
do { (attr).name[0] = '\0'; } while (0)
#define RTHREADS_PS3_JOINABLE THREAD_JOINABLE
#define rthreads_ps3_thread_create sysThreadCreate
#define rthreads_ps3_thread_join sysThreadJoin
#define rthreads_ps3_thread_detach sysThreadDetach
#define rthreads_ps3_thread_exit sysThreadExit
#define rthreads_ps3_thread_get_id sysThreadGetId
#define rthreads_ps3_thread_get_prio sysThreadGetPriority
#define rthreads_ps3_thread_yield sysThreadYield
#define rthreads_ps3_thread_set_prio sysThreadSetPriority
#define rthreads_ps3_lwmutex_create sysLwMutexCreate
#define rthreads_ps3_lwmutex_destroy sysLwMutexDestroy
#define rthreads_ps3_lwmutex_lock sysLwMutexLock
#define rthreads_ps3_lwmutex_trylock sysLwMutexTryLock
#define rthreads_ps3_lwmutex_unlock sysLwMutexUnlock
#define rthreads_ps3_lwcond_create sysLwCondCreate
#define rthreads_ps3_lwcond_destroy sysLwCondDestroy
#define rthreads_ps3_lwcond_wait sysLwCondWait
#define rthreads_ps3_lwcond_signal sysLwCondSignal
#define rthreads_ps3_lwcond_signal_all sysLwCondSignalAll
#else
typedef sys_lwmutex_attribute_t rthreads_ps3_lwmutex_attr_t;
typedef sys_lwcond_attribute_t rthreads_ps3_lwcond_attr_t;
#define RTHREADS_PS3_LWMUTEX_ATTR_INIT(attr) \
sys_lwmutex_attribute_initialize(attr)
#define RTHREADS_PS3_LWCOND_ATTR_INIT(attr) \
sys_lwcond_attribute_initialize(attr)
#define RTHREADS_PS3_JOINABLE SYS_PPU_THREAD_CREATE_JOINABLE
#define rthreads_ps3_thread_create sys_ppu_thread_create
#define rthreads_ps3_thread_yield sys_ppu_thread_yield
#define rthreads_ps3_thread_join sys_ppu_thread_join
#define rthreads_ps3_thread_detach sys_ppu_thread_detach
#define rthreads_ps3_thread_exit sys_ppu_thread_exit
#define rthreads_ps3_thread_get_id sys_ppu_thread_get_id
#define rthreads_ps3_thread_get_prio sys_ppu_thread_get_priority
#define rthreads_ps3_thread_set_prio sys_ppu_thread_set_priority
#define rthreads_ps3_lwmutex_create sys_lwmutex_create
#define rthreads_ps3_lwmutex_destroy sys_lwmutex_destroy
#define rthreads_ps3_lwmutex_lock sys_lwmutex_lock
#define rthreads_ps3_lwmutex_trylock sys_lwmutex_trylock
#define rthreads_ps3_lwmutex_unlock sys_lwmutex_unlock
#define rthreads_ps3_lwcond_create sys_lwcond_create
#define rthreads_ps3_lwcond_destroy sys_lwcond_destroy
#define rthreads_ps3_lwcond_wait sys_lwcond_wait
#define rthreads_ps3_lwcond_signal sys_lwcond_signal
#define rthreads_ps3_lwcond_signal_all sys_lwcond_signal_all
#endif
/* An lv2 wait takes microseconds, with zero standing for no timeout. */
#define RTHREADS_PS3_NO_TIMEOUT 0
#endif
#if defined(BSD) || defined(ORBIS)
#include <sys/time.h>
#endif
/* sthread_setname */
#if defined(__APPLE__)
#include <dlfcn.h>
#endif
#if defined(__linux__) && !defined(USE_WIN32_THREADS)
#include <sys/prctl.h>
#endif
#if defined(__linux__) && !defined(USE_WIN32_THREADS)
#include <sys/resource.h>
#include <unistd.h>
#if !defined(__ANDROID__) && defined(RLIMIT_RTPRIO) && defined(RLIMIT_NICE)
#define RTHREADS_HAVE_PRIO_RLIMITS 1
#endif
#endif
/* Linux: scond goes straight to the futex. The case for it on Android
* is that Bionic's condvar issues the wake syscall on every signal
* whether or not anyone is waiting, so a waiter count in front of it
* makes the common empty signal free. glibc already skips that syscall,
* but its condvar still takes its internal lock on the way to finding
* out: measured on one core, an empty scond_signal costs 3.6 ns through
* pthread_cond and 1.5 ns through the futex path, so the gate is widened
* to every Linux build rather than Bionic alone. The constants are
* spelled out here because they are ABI facts, not header facts: the
* same values from 2.6-era kernels on. */
#if defined(__linux__) && !defined(USE_WIN32_THREADS)
#include <sys/syscall.h>
#include <errno.h>
#define RTHREADS_FUTEX_SCOND 1
#define RTHREADS_FUTEX_WAIT_BITSET_PRIVATE (9 | 128)
#define RTHREADS_FUTEX_WAKE_PRIVATE (1 | 128)
#define RTHREADS_FUTEX_MATCH_ANY 0xffffffffu
#endif
/* sthread_prefer_fast_cores: Linux and Android, through the raw
* affinity syscalls so that no particular libc vintage is assumed. */
#if defined(__linux__) && !defined(USE_WIN32_THREADS)
#include <sys/syscall.h>
#include <stdio.h>
#include <unistd.h>
/* The prototype every Linux libc uses; spelled out so a strict C89
* build, where glibc hides it, sees the same one. */
extern long syscall(long number, ...);
#define RTHREADS_HAVE_AFFINITY 1
#ifndef RTHREADS_CPU_SYSFS
#define RTHREADS_CPU_SYSFS "/sys/devices/system/cpu"
#endif
#endif
#if (defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) \
|| defined(__NetBSD__) || defined(__OpenBSD__)) \
&& !defined(USE_WIN32_THREADS) && !defined(__ANDROID__)
#include <sched.h>
#define RTHREADS_HAVE_SCHEDPARAM 1
#endif
#if defined(__MACH__) && defined(__APPLE__)
#include <mach/clock.h>
#include <mach/mach.h>
#include <mach/mach_time.h>
#include <mach/thread_policy.h>
#include <sys/sysctl.h> /* sthread_get_core_topology */
#include <TargetConditionals.h>
#include <AvailabilityMacros.h> /* MAC_OS_X_VERSION_MIN_REQUIRED (since 10.2) */
/* The pthread QoS override API (pthread_override_qos_class_start_np, used by
* sthread_priority_override_*) exists only on macOS 10.10+ / iOS 8.0+, and
* RetroArch still ships deployment targets below that (OS X 10.5, iOS 6)
* where the symbol is absent in both SDK and runtime. Gate on the
* deployment-target version. TARGET_OS_* keeps the macOS check from firing
* on iOS; numeric literals are used because the MAC_OS_X_VERSION_10_10 /
* __IPHONE_8_0 constants are undefined on old SDKs (and would expand to 0). */
#if (TARGET_OS_OSX && defined(MAC_OS_X_VERSION_MIN_REQUIRED) && MAC_OS_X_VERSION_MIN_REQUIRED >= 101000) || \
(TARGET_OS_IPHONE && defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 80000)
#define RTHREADS_HAVE_QOS_OVERRIDE 1
#include <pthread/qos.h>
#endif
/* clock_gettime() arrived in macOS 10.12 / iOS 10.0 / tvOS 10.0. Below that
* the Mach clock service is the only option; see the note on
* rthreads_calendar_clock below for why it must not be re-acquired per call.
* Same literal-constant rationale as above. */
#if (TARGET_OS_OSX && defined(MAC_OS_X_VERSION_MIN_REQUIRED) && MAC_OS_X_VERSION_MIN_REQUIRED >= 101200) || \
(TARGET_OS_IPHONE && defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 100000)
#define RTHREADS_HAVE_CLOCK_GETTIME 1
#endif
#endif
#if defined(__MACH__) && defined(__APPLE__) && !defined(RTHREADS_HAVE_CLOCK_GETTIME)
/* Acquired once for the lifetime of the process.
*
* The previous code called host_get_clock_service(mach_host_self(), ...)
* followed by mach_port_deallocate() on every scond_wait_timeout(). That is
* wrong twice over:
*
* - the send right returned by mach_host_self() was never deallocated, so
* every call leaked a user reference on the host port;
* - host_get_clock_service() allocates a fresh port *name* in the task IPC
* space which is then immediately freed, so a hot caller (the CoreAudio
* write path, the task queue worker, autosave) churns the task's port
* name space continuously for the whole session.
*
* Neither is acceptable in a function called at audio-buffer rate. */
static clock_serv_t rthreads_calendar_clock;
static pthread_once_t rthreads_calendar_clock_once = PTHREAD_ONCE_INIT;
static void rthreads_calendar_clock_init(void)
{
mach_port_t host = mach_host_self();
host_get_clock_service(host, CALENDAR_CLOCK, &rthreads_calendar_clock);
mach_port_deallocate(mach_task_self(), host);
}
#endif
struct thread_data
{
void (*func)(void*);
void *userdata;
};
#if defined(USE_PSP_THREADS) || defined(USE_VITA_THREADS)
#define RTHREADS_SCE_START
#endif
/* Backends that carry the entry function and its argument in the
* sthread itself rather than in a separate thread_data block. */
#if defined(RTHREADS_SCE_START) || defined(USE_PS2_THREADS)
#define RTHREADS_NO_THREAD_DATA
#endif
#ifdef RTHREADS_SCE_START
#define PSP_THREAD_DONE 1
#define PSP_THREAD_DETACHED 2
struct psp_thread_start
{
void (*func)(void*);
void *userdata;
retro_atomic_int_t state;
};
#endif
struct sthread
{
#if defined(USE_WIN32_THREADS)
HANDLE thread;
DWORD id;
#elif defined(USE_GX_THREADS)
lwp_t id;
#elif defined(USE_CTR_THREADS)
Thread id;
#elif defined(RTHREADS_SCE_START)
struct psp_thread_start *start;
SceUID id;
#elif defined(USE_WIIU_THREADS)
OSThread *id; /* head of one allocation that also holds the stack */
#elif defined(USE_SWITCH_THREADS)
struct sthread *next; /* reap list link once detached */
Thread t;
#elif defined(USE_PS2_THREADS)
void (*func)(void*);
void *userdata;
void *stack;
struct sthread *next; /* reap list link once detached */
s32 id;
s32 done; /* semaphore the exit path signals for a joiner */
retro_atomic_int_t state;
#elif defined(USE_PS3_THREADS)
sys_ppu_thread_t id;
#else
pthread_t id;
#endif
};
struct slock
{
#if defined(USE_WIN32_THREADS)
CRITICAL_SECTION lock;
#elif defined(USE_GX_THREADS)
mutex_t lock;
#elif defined(USE_CTR_THREADS)
LightLock lock;
#elif defined(USE_PSP_THREADS)
SceUID lock;
#elif defined(USE_VITA_THREADS)
SceKernelLwMutexWork lock;
#elif defined(USE_WIIU_THREADS)
OSFastMutex lock;
#elif defined(USE_SWITCH_THREADS)
Mutex lock;
#elif defined(USE_PS2_THREADS)
s32 lock; /* binary semaphore */
#elif defined(USE_PS3_THREADS)
sys_lwmutex_t lock;
#else
pthread_mutex_t lock;
#endif
};
#ifdef USE_WIN32_THREADS
/* Win32 condition variable.
*
* The same shape as the one in ntdll: the scond is one word holding a
* lock-free list of waiter blocks that live on the waiting threads'
* stacks, each block with a flag word of its own. A wait pushes its
* block with a single compare-and-swap, releases the caller's lock,
* spins for a bounded time on its own flag word, and only then blocks
* in the kernel; a wake takes the block off the list and flips the flag,
* and issues a kernel wake only for a waiter that had committed to
* blocking, which is what makes a back-to-back wake cost no syscall at
* all. The spin is the hardware wait where there is one - mwaitx on AMD
* and umwait on Intel park the core on the flag word's cache line with
* a TSC deadline, so a sibling hyperthread is not disturbed - and a
* pause loop elsewhere; a single-processor machine does not spin.
*
* The kernel wait is chosen once per process from what ntdll exports:
* NtWaitForAlertByThreadId / NtAlertThreadByThreadId (Windows 8 and
* later), the keyed event pair (XP and later), and an auto-reset event
* per thread where neither exists (the Xbox targets). RTHREADS_SCOND in
* the environment picks one by name (alert, keyed, event),
* RTHREADS_SCOND_SPIN a spin mode (none, pause, mwaitx, umwait) and
* RTHREADS_SCOND_SPIN_CYCLES the TSC bound, for measurement.
*
* The spin defaults to the pause loop even where mwaitx or umwait exist:
* measured against the native variable at 600 dispatches a frame on a
* 9950X3D, pause is ahead of mwaitx at every thread count, because the
* hardware wait's exit latency is on the order of the whole interval
* between dispatches. The hardware waits remain selectable.
*
* Bit 0 of the scond word is a lock that wakers and timed-out waiters
* take to edit the list; pushes never take it. Signal wakes the oldest
* waiter, broadcast takes the whole list with one compare-and-swap. */
#define SCOND_HEAD_LOCK ((uintptr_t)1)
#define SCOND_HEAD_MASK (~(uintptr_t)1)
#define SCOND_W_WOKEN 1 /* a waker has taken this block */
#define SCOND_W_ASLEEP 2 /* the waiter committed to the kernel wait */
struct scond_waiter
{
struct scond_waiter *next;
HANDLE event; /* event fallback only */
retro_atomic_int_t flags;
DWORD tid;
};
enum scond_sleep_kind
{
SCOND_SLEEP_ALERT = 1,
SCOND_SLEEP_KEYED,
SCOND_SLEEP_EVENT
};
enum scond_spin_kind
{
SCOND_SPIN_NONE = 0,
SCOND_SPIN_PAUSE,
SCOND_SPIN_MWAITX,
SCOND_SPIN_UMWAIT
};
typedef LONG (NTAPI *scond_nt_wait_alert_t)(volatile void *hint,
LARGE_INTEGER *timeout);
typedef LONG (NTAPI *scond_nt_alert_tid_t)(HANDLE tid);
typedef LONG (NTAPI *scond_nt_keyed_t)(HANDLE h, void *key, BOOLEAN alertable,
LARGE_INTEGER *timeout);
typedef LONG (NTAPI *scond_nt_create_keyed_t)(HANDLE *h, ULONG access,
void *attr, ULONG flags);
typedef HANDLE (WINAPI *scond_create_timer_ex_t)(LPSECURITY_ATTRIBUTES,
LPCWSTR, DWORD, DWORD);
/* The APC routine and its argument are always NULL here, so they are
* typed as LPVOID rather than depending on PTIMERAPCROUTINE */
typedef BOOL (WINAPI *scond_set_timer_t)(HANDLE, const LARGE_INTEGER*, LONG,
LPVOID, LPVOID, BOOL);
#define SCOND_TIMER_HIGH_RESOLUTION 0x00000002 /* CREATE_WAITABLE_TIMER_HIGH_RESOLUTION */
#define SCOND_TIMER_ALL_ACCESS 0x001F0003 /* TIMER_ALL_ACCESS */
static struct
{
scond_nt_wait_alert_t wait_alert;
scond_nt_alert_tid_t alert_tid;
scond_nt_keyed_t wait_keyed;
scond_nt_keyed_t release_keyed;
HANDLE keyed;
retro_atomic_int_t state; /* 0 unresolved, 1 resolving, 2 ready */
int sleep;
int spin;
unsigned spin_cycles; /* TSC bound for the hardware waits */
unsigned spin_iters; /* iteration bound for the pause loop */
DWORD tls_event;
/* Bounded waits: a kernel timeout ends on the system timer's tick,
* 15.6 ms unless something in the process has lowered it, so a
* bounded wait also sleeps on a high resolution waitable timer, one
* per thread, together with the thread's event. Unset where the
* timer cannot be had (before Windows 10 1803): the kernel timeout
* then bounds the wait, as before. */
scond_create_timer_ex_t create_timer_ex;
scond_set_timer_t set_timer;
DWORD tls_timer;
bool hires;
} scond_g;
static void scond_global_init(void);
#endif
#ifdef USE_WIIU_THREADS
/* One node per blocked waiter, living on that waiter's stack for
* exactly the duration of the wait. An auto-reset event both parks
* the waiter and carries the wake, so a signal issued between
* enqueue and OSWaitEvent latches instead of getting lost. */
struct wiiu_cond_waiter
{
struct wiiu_cond_waiter *next;
OSEvent ev;
};
#endif
#ifdef USE_PS2_THREADS
/* One node per blocked waiter, on that waiter's stack for the
* duration of the wait. The EE kernel counts wakeups per thread, so
* a WakeupThread issued between enqueue and SleepThread is kept
* rather than lost; the timed variant arms a timer alarm whose
* handler, in interrupt context, marks the node and wakes the
* thread. The lists are guarded by disabling interrupts: the kernel
* never time-slices, so on a single core that is the only way a
* critical section this short can be entered twice. */
struct ps2_cond_waiter
{
struct ps2_cond_waiter *next;
s32 tid;
volatile int fired;
};
#endif
struct scond
{
#if defined(USE_WIN32_THREADS)
retro_atomic_ptr_t head; /* struct scond_waiter * | SCOND_HEAD_LOCK */
#elif defined(USE_GX_THREADS)
cond_t cond;
#elif defined(USE_CTR_THREADS)
CondVar cond;
#elif defined(USE_PSP_THREADS)
SceUID gate; /* binary semaphore guarding waiters */
SceUID sema; /* wait tokens, one credit per wake */
int waiters;
#elif defined(USE_VITA_THREADS)
SceKernelLwCondWork work;
SceKernelLwMutexWork *assoc; /* the lock this condvar is bound to */
retro_atomic_int_t bound; /* nonzero once work exists */
#elif defined(USE_WIIU_THREADS)
struct wiiu_cond_waiter *head; /* FIFO of parked waiters */
struct wiiu_cond_waiter *tail;
OSFastMutex gate;
#elif defined(USE_SWITCH_THREADS)
CondVar cond;
#elif defined(USE_PS2_THREADS)
struct ps2_cond_waiter *head; /* FIFO of sleeping waiters */
struct ps2_cond_waiter *tail;
#elif defined(RTHREADS_FUTEX_SCOND)
retro_atomic_int_t seq; /* bumped by every signal; futex word */
retro_atomic_int_t waiters; /* threads between enqueue and wake */
#elif defined(USE_PS3_THREADS)
sys_lwcond_t work;
sys_lwmutex_t *assoc; /* the lock this condvar is bound to */
retro_atomic_int_t bound; /* nonzero once work exists */
#else
pthread_cond_t cond;
#endif
};
#ifdef USE_PS2_THREADS
#define PS2_THREAD_DONE 1
#define PS2_THREAD_DETACHED 2
static sthread_t *ps2_reap_list;
/* Detached threads park here until their DONE bit is set, since the
* EE kernel frees only the control block on exit and the stack is
* ours to release from another thread. */
static void ps2_reap(void)
{
sthread_t *done = NULL, **pp;
DIntr();
pp = &ps2_reap_list;
while (*pp)
{
sthread_t *z = *pp;
if (retro_atomic_load_acquire_int(&z->state) & PS2_THREAD_DONE)
{
*pp = z->next;
z->next = done;
done = z;
}
else
pp = &z->next;
}
EIntr();
while (done)
{
sthread_t *z = done;
done = z->next;
DeleteSema(z->done);
free(z->stack);
free(z);
}
}
static void ps2_thread_wrap(void *arg)
{
sthread_t *t = (sthread_t*)arg;
s32 done = t->done;
t->func(t->userdata);
/* From here to the exit syscall nothing may run in this thread's
* place: the joiner or the reaper frees the stack it is still
* standing on. Priority 0 is the top of the band and the EE
* kernel never time-slices equal priorities, so signaling below
* readies the joiner without switching to it. Nothing in t is
* touched once DONE is published. */
ChangeThreadPriority(GetThreadId(), 0);
if (!(retro_atomic_fetch_or_int(&t->state, PS2_THREAD_DONE)
& PS2_THREAD_DETACHED))
SignalSema(done);
ExitDeleteThread();
}
static u64 ps2_cond_alarm(s32 id, u64 scheduled, u64 actual,
void *arg, void *pc)
{
struct ps2_cond_waiter *w = (struct ps2_cond_waiter*)arg;
(void)id;
(void)scheduled;
(void)actual;
(void)pc;
w->fired = 1;
iWakeupThread(w->tid);
return 0; /* one-shot */
}
static void ps2_cond_enqueue(scond_t *cond, struct ps2_cond_waiter *w)
{
w->next = NULL;
w->fired = 0;
w->tid = GetThreadId();
DIntr();
if (cond->tail)
cond->tail->next = w;
else
cond->head = w;
cond->tail = w;
EIntr();
}
/* Removes w if it is still queued; returns whether it was. */
static bool ps2_cond_withdraw(scond_t *cond, struct ps2_cond_waiter *w)
{
struct ps2_cond_waiter *cur, *prev = NULL;
DIntr();
cur = cond->head;
while (cur && cur != w)
{
prev = cur;
cur = cur->next;
}
if (cur)
{
if (prev)
prev->next = w->next;
else
cond->head = w->next;
if (cond->tail == w)
cond->tail = prev;
}
EIntr();
return cur != NULL;
}
#endif
#ifdef USE_SWITCH_THREADS
/* libnx has no detach: a Thread stays live until threadClose, and a
* thread cannot close itself, since that unmaps the stack it is
* running on. Detached threads park here and are reaped by the next
* create or detach once their handle has signaled, which bounds what
* lingers to the detached threads that finished since the last such
* call. */
static Mutex switch_reap_lock;
static sthread_t *switch_reap_list;
static void switch_reap(void)
{
sthread_t **pp;
mutexLock(&switch_reap_lock);
pp = &switch_reap_list;
while (*pp)
{
sthread_t *z = *pp;
if (svcWaitSynchronizationSingle(z->t.handle, 0) == 0)
{
*pp = z->next;
threadClose(&z->t);
free(z);
}
else
pp = &z->next;
}
mutexUnlock(&switch_reap_lock);
}
#endif
#if defined(RTHREADS_SCE_START)
/* Joinable threads finish into the dormant state and are reaped by
* sthread_join. A detached thread reaps itself: whichever of the
* finishing thread and sthread_detach runs second sees the other's
* bit and frees the start block, and self-reaping also removes the
* kernel thread so nothing stays dormant. */
static int psp_thread_wrap(SceSize args_size, void *argp)
{
struct psp_thread_start *s = *(struct psp_thread_start**)argp;
(void)args_size;
s->func(s->userdata);
if (retro_atomic_fetch_or_int(&s->state, PSP_THREAD_DONE)
& PSP_THREAD_DETACHED)
{
free(s);
sceKernelExitDeleteThread(0);
}
return 0;
}
#elif defined(USE_CTR_THREADS) || defined(USE_SWITCH_THREADS)
static void thread_wrap(void *data_)
{
struct thread_data *data = (struct thread_data*)data_;
if (!data)
return;
data->func(data->userdata);
free(data);
}
#elif defined(USE_WIIU_THREADS)
static int thread_wrap(int argc, const char **argv)
{
struct thread_data *data = (struct thread_data*)argv;
(void)argc;
if (!data)
return 0;
data->func(data->userdata);
free(data);
return 0;
}
/* Runs on the scheduler once the thread is fully dead - after a join,
* or after exit for a detached thread - so the control block and the
* stack are never freed while the thread could still touch them. The
* OSThread heads the allocation. */
static void wiiu_thread_dealloc(OSThread *thread, void *stack)
{
(void)stack;
free(thread);
}
#elif defined(USE_PS2_THREADS)
/* ps2_thread_wrap above takes the sthread itself as its argument. */
#elif defined(USE_PS3_THREADS)
/* The entry point takes its argument as a pointer under PSL1GHT and as
* the raw register value under the PS3 SDK; it arrives in the same
* place either way. An lv2 PPU thread leaves through the exit syscall,
* so returning from here is not an option. */
#ifdef __PSL1GHT__
static void thread_wrap(void *data_)
#else
static void thread_wrap(uint64_t data_)
#endif
{
struct thread_data *data = (struct thread_data*)(uintptr_t)data_;
if (data)
{
data->func(data->userdata);
free(data);
}
rthreads_ps3_thread_exit(0);
}
#else
#ifdef USE_WIN32_THREADS
static DWORD CALLBACK thread_wrap(void *data_)
#else
static void *thread_wrap(void *data_)
#endif
{
struct thread_data *data = (struct thread_data*)data_;
if (!data)
return 0;
data->func(data->userdata);
free(data);
return 0;
}
#endif
static sthread_t *sthread_create_ex(void (*thread_func)(void*),
void *userdata, int thread_priority, size_t stack_size);
sthread_t *sthread_create(void (*thread_func)(void*), void *userdata)
{
return sthread_create_ex(thread_func, userdata, 0, 0);
}
sthread_t *sthread_create_with_priority(void (*thread_func)(void*), void *userdata, int thread_priority)
{
return sthread_create_ex(thread_func, userdata, thread_priority, 0);
}
sthread_t *sthread_create_with_stack_size(void (*thread_func)(void*), void *userdata, size_t stack_size)
{
return sthread_create_ex(thread_func, userdata, 0, stack_size);
}
#if !defined(USE_WIN32_THREADS) && !defined(USE_GX_THREADS) \
&& !defined(USE_CTR_THREADS) && !defined(USE_PSP_THREADS) \
&& !defined(USE_VITA_THREADS) && !defined(USE_WIIU_THREADS) \
&& !defined(USE_SWITCH_THREADS) && !defined(USE_PS2_THREADS) \
&& !defined(USE_PS3_THREADS) && !defined(__HAIKU__) \
&& !defined(__EMSCRIPTEN__)
#define HAVE_THREAD_ATTR
#endif
static sthread_t *sthread_create_ex(void (*thread_func)(void*),
void *userdata, int thread_priority, size_t stack_size)
{
#if defined(STACKSIZE)
/* 0 keeps the backend's own default. This is the console case: each
* takes a size at create, and STACKSIZE is its default. Windows takes
* stack_size directly and pthread takes it through the attr. */
const size_t stack = stack_size ? stack_size : (size_t)STACKSIZE;
#endif
#ifdef HAVE_THREAD_ATTR
pthread_attr_t thread_attr;
bool thread_attr_needed = false;
#endif
bool thread_created = false;
#ifndef RTHREADS_NO_THREAD_DATA
struct thread_data *data = NULL;
#endif
sthread_t *thread = (sthread_t*)malloc(sizeof(*thread));
if (!thread)
return NULL;
#ifndef RTHREADS_NO_THREAD_DATA
if (!(data = (struct thread_data*)malloc(sizeof(*data))))
{
free(thread);
return NULL;
}
data->func = thread_func;
data->userdata = userdata;
#endif
#if defined(USE_WIN32_THREADS)
thread->id = 0;
thread->thread = CreateThread(NULL, stack_size, thread_wrap,
data, 0, &thread->id);
thread_created = !!thread->thread;
#elif defined(USE_GX_THREADS)
{
/* LWP priorities run 0..127 with higher numbers scheduled
* first; workers default to the middle of the band. */
u8 prio = 64;
if (thread_priority >= 1 && thread_priority <= 100)
prio = (u8)(1 + ((thread_priority - 1) * 126) / 99);
thread->id = LWP_THREAD_NULL;
thread_created = LWP_CreateThread(&thread->id, thread_wrap,
data, NULL, stack, prio) == 0;
}
#elif defined(USE_CTR_THREADS)
{
/* Userland priorities run 0x18..0x3F with lower numbers
* scheduled first. A worker sits one step above its creator so
* the work it is handed is acted on promptly; an explicit
* rthreads priority maps across the whole band instead. New3DS
* gets workers on the third core, which the application owns
* outright; elsewhere the exheader's default core is used. */
s32 prio = 0x30;
int core_id = -2;
bool new3ds = false;
APT_CheckNew3DS(&new3ds);
if (new3ds)
core_id = 2;
svcGetThreadPriority(&prio, CUR_THREAD_HANDLE);
if (thread_priority >= 1 && thread_priority <= 100)
prio = 0x3F - ((thread_priority - 1) * (0x3F - 0x18)) / 99;
else
prio = prio - 1;
if (prio < 0x18)
prio = 0x18;
if (prio > 0x3F)
prio = 0x3F;
thread->id = threadCreate(thread_wrap, data,
stack, prio, core_id, false);
thread_created = !!thread->id;
}
#elif defined(USE_PSP_THREADS)
{
/* Priorities run 0x10..0x6F with lower numbers scheduled first.
* Workers inherit the creator's priority; an explicit rthreads
* priority maps across the whole band instead. The kernel copies
* the argument block when the thread starts, so the pointer to
* the start block travels by value. */
struct psp_thread_start *start = (struct psp_thread_start*)
malloc(sizeof(*start));
int prio;
if (!start)
{
free(thread);
return NULL;
}
start->func = thread_func;
start->userdata = userdata;
retro_atomic_int_init(&start->state, 0);
prio = sceKernelGetThreadCurrentPriority();
if (thread_priority >= 1 && thread_priority <= 100)
prio = 0x6F - ((thread_priority - 1) * (0x6F - 0x10)) / 99;
if (prio < 0x10)
prio = 0x10;
if (prio > 0x6F)
prio = 0x6F;
thread->start = start;
thread->id = sceKernelCreateThread("rarch_thread",
psp_thread_wrap, prio, stack,
PSP_THREAD_ATTR_USER | PSP_THREAD_ATTR_VFPU, NULL);
if (thread->id >= 0)
{
if (sceKernelStartThread(thread->id, sizeof(start), &start) >= 0)
thread_created = true;
else
sceKernelDeleteThread(thread->id);
}
if (!thread_created)
free(start);
}
#elif defined(USE_PS2_THREADS)
{
/* Priorities run 0..127 with lower numbers scheduled first and
* no time-slicing within a level. Workers inherit the creator's
* priority; an explicit rthreads priority maps across 1..127.
* The stack is ours, handed to the kernel with its size, and
* the sthread itself is the start argument. */
ee_thread_t th;
ee_sema_t sema;
ee_thread_status_t st;
int prio;
ps2_reap();
thread->func = thread_func;
thread->userdata = userdata;
thread->next = NULL;
thread->id = -1;
thread->done = -1;
retro_atomic_int_init(&thread->state, 0);
thread->stack = memalign(16, stack);
if (!thread->stack)
{
free(thread);
return NULL;
}
memset(&sema, 0, sizeof(sema));
sema.init_count = 0;
sema.max_count = 1;
thread->done = CreateSema(&sema);
memset(&st, 0, sizeof(st));
ReferThreadStatus(GetThreadId(), &st);
prio = st.current_priority;
if (thread_priority >= 1 && thread_priority <= 100)
prio = 127 - ((thread_priority - 1) * 126) / 99;
if (prio < 0)
prio = 0;
if (prio > 127)
prio = 127;
memset(&th, 0, sizeof(th));
th.func = (void*)ps2_thread_wrap;
th.stack = thread->stack;
th.stack_size = stack;
th.gp_reg = &_gp;
th.initial_priority = prio;
if (thread->done >= 0)
thread->id = CreateThread(&th);
if (thread->id >= 0)
{
if (StartThread(thread->id, thread) >= 0)
thread_created = true;
else
DeleteThread(thread->id);
}
if (!thread_created)
{
if (thread->done >= 0)
DeleteSema(thread->done);
free(thread->stack);
}
}
#elif defined(USE_SWITCH_THREADS)
{
/* Priorities run 0x00..0x3F with lower numbers scheduled first,
* within whatever band the process is allowed; 0x2C is where
* the main thread runs. Workers inherit the creator's priority,
* and an explicit rthreads priority maps across 0x1C..0x3F,
* falling back to inheriting if the kernel refuses it. libnx
* allocates and maps the stack itself, so the thread lands on
* the process's default core. */
s32 prio = 0x2C;
Result rc;
switch_reap();
svcGetThreadPriority(&prio, CUR_THREAD_HANDLE);
if (thread_priority >= 1 && thread_priority <= 100)
{
s32 want = 0x3F - ((thread_priority - 1) * (0x3F - 0x1C)) / 99;
rc = threadCreate(&thread->t, thread_wrap, data,
NULL, stack, want, -2);
if (R_FAILED(rc))
rc = threadCreate(&thread->t, thread_wrap, data,
NULL, stack, prio, -2);
}
else
rc = threadCreate(&thread->t, thread_wrap, data,
NULL, stack, prio, -2);
if (R_SUCCEEDED(rc))
{
if (R_SUCCEEDED(threadStart(&thread->t)))
thread_created = true;
else
threadClose(&thread->t);
}
}
#elif defined(USE_WIIU_THREADS)
{
/* Priorities run 0..31 with lower numbers scheduled first;
* workers inherit the creator's priority, and an explicit
* rthreads priority maps across the whole band. The control
* block and the stack are one caller-owned allocation, handed
* back by the deallocator; the stack pointer passed in is its
* top, since PowerPC stacks grow down. */
int32_t prio;
uint8_t *block = (uint8_t*)memalign(16,
sizeof(OSThread) + stack);
if (!block)
{
free(data);
free(thread);
return NULL;
}
memset(block, 0, sizeof(OSThread));
prio = OSGetThreadPriority(OSGetCurrentThread());
if (thread_priority >= 1 && thread_priority <= 100)
prio = 31 - ((thread_priority - 1) * 31) / 99;
if (prio < 0)
prio = 0;
if (prio > 31)
prio = 31;
thread->id = (OSThread*)block;
if (OSCreateThread(thread->id, thread_wrap, 0, (char*)data,
block + sizeof(OSThread) + stack, stack, prio,
OS_THREAD_ATTRIB_AFFINITY_ANY))
{
OSSetThreadDeallocator(thread->id, wiiu_thread_dealloc);
OSResumeThread(thread->id);
thread_created = true;
}
else
free(block);
}
#elif defined(USE_VITA_THREADS)
{
/* Priorities run 64..191 with lower numbers scheduled first.
* Workers inherit the creator's priority; an explicit rthreads
* priority maps across the whole band instead. The kernel
* copies the argument block when the thread starts, so the
* pointer to the start block travels by value. */
struct psp_thread_start *start = (struct psp_thread_start*)
malloc(sizeof(*start));
int prio;
if (!start)
{
free(thread);
return NULL;
}
start->func = thread_func;
start->userdata = userdata;
retro_atomic_int_init(&start->state, 0);
prio = sceKernelGetThreadCurrentPriority();
if (thread_priority >= 1 && thread_priority <= 100)
prio = 191 - ((thread_priority - 1) * (191 - 64)) / 99;
if (prio < 64)
prio = 64;
if (prio > 191)
prio = 191;
thread->start = start;
thread->id = sceKernelCreateThread("rarch_thread",
psp_thread_wrap, prio, stack, 0, 0, NULL);
if (thread->id >= 0)
{
if (sceKernelStartThread(thread->id, sizeof(start), &start) >= 0)
thread_created = true;
else
sceKernelDeleteThread(thread->id);
}
if (!thread_created)
free(start);
}
#elif defined(USE_PS3_THREADS)
{
/* lv2 priorities run 0..3071 with lower numbers scheduled first;
* the primary thread sits at 1000. Workers inherit the creator's
* priority, and an explicit rthreads priority maps across
* 2047..512, keeping even the highest request below the band the
* system's own threads run in. */
sys_ppu_thread_t self = 0;
int prio = 1000;
rthreads_ps3_thread_get_id(&self);
rthreads_ps3_thread_get_prio(self, &prio);
if (thread_priority >= 1 && thread_priority <= 100)
prio = 2047 - ((thread_priority - 1) * (2047 - 512)) / 99;
if (prio < 0)
prio = 0;
if (prio > 3071)
prio = 3071;
thread->id = 0;
thread_created = rthreads_ps3_thread_create(&thread->id, thread_wrap,
#ifdef __PSL1GHT__
data,
#else
(uint64_t)(uintptr_t)data,
#endif
prio, stack, RTHREADS_PS3_JOINABLE,
(char*)"rarch_thread") == 0;
}
#else
thread->id = 0;
#ifdef HAVE_THREAD_ATTR
pthread_attr_init(&thread_attr);
/* os/gekko's threads take no scheduling policy. */
#ifndef GEKKO_NATIVE
if ((thread_priority >= 1) && (thread_priority <= 100))
{
struct sched_param sp;
memset(&sp, 0, sizeof(struct sched_param));
sp.sched_priority = thread_priority;
pthread_attr_setschedpolicy(&thread_attr, SCHED_RR);
pthread_attr_setschedparam(&thread_attr, &sp);
thread_attr_needed = true;
}
#endif
#if defined(__APPLE__)
/* Default stack size on Apple is 512Kb;
* for PS2 disc scanning and other reasons, we'd like 2MB. A caller
* asking for more than that gets it below. */
pthread_attr_setstacksize(&thread_attr, stack_size > 0x200000 ? stack_size : 0x200000);
thread_attr_needed = true;
#else
if (stack_size)
{
pthread_attr_setstacksize(&thread_attr, stack_size);
thread_attr_needed = true;
}
#endif
if (thread_attr_needed)
thread_created = pthread_create(&thread->id, &thread_attr, thread_wrap, data) == 0;
else
thread_created = pthread_create(&thread->id, NULL, thread_wrap, data) == 0;
pthread_attr_destroy(&thread_attr);
#else
thread_created = pthread_create(&thread->id, NULL, thread_wrap, data) == 0;
#endif
#endif
if (thread_created)
return thread;
#ifndef RTHREADS_NO_THREAD_DATA
free(data);
#endif
free(thread);
return NULL;
}
#ifdef RTHREADS_HAVE_AFFINITY
/* The fast cores of an asymmetric part, intersected with what this
* thread may already run on. Computed once; on a homogeneous part it
* comes out equal to the allowed set and the pin is skipped.
*
* The class of each processor comes from cpu_class.h, the same read
* cpu_features_get_processor_order() ranks by, so a thread pinned
* here and one a core pins to "the strongest processor" agree on
* which silicon that is. */
#if defined(RTHREADS_CPU_SYSFS) && !defined(CPU_CLASS_SYSFS)
#define CPU_CLASS_SYSFS RTHREADS_CPU_SYSFS
#endif
#include "../features/cpu_class.h"
#define RTHREADS_MASK_WORDS 4
#define RTHREADS_MASK_BITS (RTHREADS_MASK_WORDS * 8 * (int)sizeof(unsigned long))
static unsigned long rthreads_fast_mask[RTHREADS_MASK_WORDS];
static int rthreads_fast_state; /* 0 unknown, 1 pin, -1 no-op */
#define RTHREADS_MASK_TEST(m, i) \
(((m)[(i) / (8 * sizeof(unsigned long))] >> ((i) % (8 * sizeof(unsigned long)))) & 1ul)
#define RTHREADS_MASK_SET(m, i) \
((m)[(i) / (8 * sizeof(unsigned long))] |= 1ul << ((i) % (8 * sizeof(unsigned long))))
/* Marks in cls every processor of the highest class; returns whether
* the platform published any class at all. */
static bool rthreads_read_fast_class(unsigned long *cls)
{
unsigned char klass[CPU_CLASS_MAX_IDS];
unsigned char best = 0;
size_t i, n = cpu_class_read(klass, sizeof(klass));
memset(cls, 0, RTHREADS_MASK_WORDS * sizeof(unsigned long));
if (!n)
return false;
for (i = 0; i < n; i++)
if (klass[i] > best)
best = klass[i];
for (i = 0; i < n && i < (size_t)RTHREADS_MASK_BITS; i++)
if (klass[i] == best)
RTHREADS_MASK_SET(cls, i);
return true;
}
/* Classifies the system's cores into fast, intersects with allowed,
* and stores the result in fast. Returns 1 when the thread should be
* pinned to fast and -1 when it should be left alone: nothing was
* readable, every allowed CPU is a fast one (a homogeneous part, or
* an affinity already inside the fast set), or none is. */
static int rthreads_classify_fast_cores(const unsigned long *allowed,
unsigned long *fast)
{
unsigned long cls[RTHREADS_MASK_WORDS];
unsigned i;
bool any = false, all = true;
memset(fast, 0, RTHREADS_MASK_WORDS * sizeof(unsigned long));
if (!rthreads_read_fast_class(cls))
return -1;
for (i = 0; i < (unsigned)RTHREADS_MASK_BITS; i++)
{
if (!RTHREADS_MASK_TEST(allowed, i))
continue;
if (RTHREADS_MASK_TEST(cls, i))
{
RTHREADS_MASK_SET(fast, i);
any = true;
}
else
all = false;
}
return (any && !all) ? 1 : -1;
}
static void rthreads_find_fast_cores(void)
{
unsigned long allowed[RTHREADS_MASK_WORDS];
memset(allowed, 0, sizeof(allowed));
if (syscall(__NR_sched_getaffinity, 0, sizeof(allowed), allowed) <= 0)
{
rthreads_fast_state = -1;
return;
}
rthreads_fast_state = rthreads_classify_fast_cores(allowed, rthreads_fast_mask);
}
/* Counts the physical cores in allowed, split by class. A core is the
* set in its thread_siblings_list; without topology files every CPU
* is its own core. Without a readable class every core is fast.
* Returns whether any allowed CPU was found. */
static bool rthreads_count_cores_masked(const unsigned long *allowed,
unsigned *fast, unsigned *slow)
{
unsigned long cls[RTHREADS_MASK_WORDS];
unsigned long seen[RTHREADS_MASK_WORDS];
bool have_cls = rthreads_read_fast_class(cls);
unsigned i, n = 0;
*fast = *slow = 0;
memset(seen, 0, sizeof(seen));
for (i = 0; i < (unsigned)RTHREADS_MASK_BITS; i++)
{
char path[512];
unsigned char sibs[CPU_CLASS_MAX_IDS];
unsigned j;
bool core_fast = false;
if (!RTHREADS_MASK_TEST(allowed, i) || RTHREADS_MASK_TEST(seen, i))
continue;
n++;
/* The core is fast if any of its allowed threads is. */
memset(sibs, 0, sizeof(sibs));
snprintf(path, sizeof(path),
CPU_CLASS_SYSFS "/cpu%u/topology/thread_siblings_list", i);
if (!cpu_class_sysfs_cpulist(path, sibs, sizeof(sibs)))
sibs[i] = 1;
for (j = 0; j < (unsigned)RTHREADS_MASK_BITS && j < CPU_CLASS_MAX_IDS; j++)
{
if (!sibs[j])
continue;
RTHREADS_MASK_SET(seen, j);
if (RTHREADS_MASK_TEST(allowed, j)
&& (!have_cls || RTHREADS_MASK_TEST(cls, j)))
core_fast = true;
}
if (core_fast)
(*fast)++;
else
(*slow)++;
}
return n > 0;
}
#endif
#ifdef USE_WIN32_THREADS
/* Windows 10 1607+ describes every logical CPU through
* GetSystemCpuSetInformation, whose EfficiencyClass rises with
* performance, and SetThreadSelectedCpuSets is a soft preference the
* scheduler may still override under pressure - the right strength
* for "prefer". Both are resolved at run time so builds that still
* target older Windows keep linking and simply report false there,
* and the records are read by offset since older SDKs lack the type. */
typedef BOOL (WINAPI *rthreads_get_cpusets_t)(void*, ULONG, ULONG*,
HANDLE, ULONG);
typedef BOOL (WINAPI *rthreads_set_cpusets_t)(HANDLE, const ULONG*, ULONG);
static ULONG rthreads_fast_ids[64];
static ULONG rthreads_fast_count;
static int rthreads_fast_state; /* 0 unknown, 1 pin, -1 no-op */
static rthreads_set_cpusets_t rthreads_set_cpusets;
static void rthreads_find_fast_cores(void)
{
HMODULE k32 = GetModuleHandleA("kernel32.dll");
rthreads_get_cpusets_t getinfo = NULL;
unsigned char *buf = NULL;
ULONG len = 0;
ULONG off;
BYTE best = 0;
ULONG total = 0;
rthreads_fast_state = -1;
if (!k32)
return;
getinfo = (rthreads_get_cpusets_t)(void (*)(void))
GetProcAddress(k32, "GetSystemCpuSetInformation");
rthreads_set_cpusets = (rthreads_set_cpusets_t)(void (*)(void))
GetProcAddress(k32, "SetThreadSelectedCpuSets");
if (!getinfo || !rthreads_set_cpusets)
return;
getinfo(NULL, 0, &len, GetCurrentProcess(), 0);
if (!len || !(buf = (unsigned char*)malloc(len)))
return;
if (!getinfo(buf, len, &len, GetCurrentProcess(), 0))
{
free(buf);
return;
}
/* SYSTEM_CPU_SET_INFORMATION: Size at 0, Type at 4 (0 = CpuSet),
* CpuSet.Id at 8, CpuSet.EfficiencyClass at 18. */
for (off = 0; off + 20 <= len; )
{
DWORD size = *(DWORD*)(buf + off);
if (size < 20)
break;
if (*(DWORD*)(buf + off + 4) == 0)
{
BYTE cls = buf[off + 18];
total++;
if (cls > best)
{
best = cls;
rthreads_fast_count = 0;
}
if (cls == best && rthreads_fast_count
< sizeof(rthreads_fast_ids) / sizeof(rthreads_fast_ids[0]))
rthreads_fast_ids[rthreads_fast_count++]
= *(DWORD*)(buf + off + 8);
}
off += size;
}
free(buf);
/* Every CPU in the top class means a homogeneous part. */
if (rthreads_fast_count && rthreads_fast_count < total)
rthreads_fast_state = 1;
}
#endif
#ifdef USE_WIN32_THREADS
/* Physical cores by class. CPU Sets carry CoreIndex (offset 15) and
* EfficiencyClass (offset 18) per logical processor: one core per
* (Group, CoreIndex), fast when its class is the highest present.
* Before CPU Sets (Windows 10 1607) GetLogicalProcessorInformation
* counts cores and every one is fast. */
static bool rthreads_win32_core_topology(unsigned *fast, unsigned *slow)
{
HMODULE k32 = GetModuleHandleA("kernel32.dll");
rthreads_get_cpusets_t getinfo = NULL;
unsigned char *buf = NULL;
ULONG len = 0;
ULONG off;
BYTE best = 0;
unsigned short key[256];
BYTE keycls[256];
unsigned nkey = 0, i;
*fast = *slow = 0;
if (k32)
getinfo = (rthreads_get_cpusets_t)(void (*)(void))
GetProcAddress(k32, "GetSystemCpuSetInformation");
if (getinfo)
{
getinfo(NULL, 0, &len, GetCurrentProcess(), 0);
if (len && (buf = (unsigned char*)malloc(len))
&& getinfo(buf, len, &len, GetCurrentProcess(), 0))
{
for (off = 0; off + 20 <= len; )
{
DWORD size = *(DWORD*)(buf + off);
if (size < 20)
break;
if (*(DWORD*)(buf + off + 4) == 0)
{
unsigned short k = (unsigned short)
((*(WORD*)(buf + off + 12) << 8) | buf[off + 15]);
BYTE cls = buf[off + 18];
for (i = 0; i < nkey; i++)
if (key[i] == k)
break;
if (i == nkey && nkey < sizeof(key) / sizeof(key[0]))
{
key[nkey] = k;
keycls[nkey] = cls;
nkey++;
}
else if (i < nkey && cls > keycls[i])
keycls[i] = cls;
if (cls > best)
best = cls;
}
off += size;
}
}
if (buf)
free(buf);
if (nkey)
{
for (i = 0; i < nkey; i++)
{
if (keycls[i] == best)
(*fast)++;
else
(*slow)++;
}
return true;
}
}
/* Windows 7 to 10 1511: no core classes. */
{
SYSTEM_LOGICAL_PROCESSOR_INFORMATION *info = NULL;
DWORD n = 0;
GetLogicalProcessorInformation(NULL, &n);
if (n && (info = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION*)malloc(n)))
{
if (GetLogicalProcessorInformation(info, &n))
{
DWORD c;
for (c = 0; c < n / sizeof(*info); c++)
if (info[c].Relationship == RelationProcessorCore)
(*fast)++;
}
free(info);
}
}
return *fast > 0;
}
#endif
bool sthread_get_core_topology(unsigned *fast, unsigned *slow)
{
#if defined(RTHREADS_HAVE_AFFINITY)
unsigned long allowed[RTHREADS_MASK_WORDS];
memset(allowed, 0, sizeof(allowed));
if (syscall(__NR_sched_getaffinity, 0, sizeof(allowed), allowed) <= 0)
return false;
return rthreads_count_cores_masked(allowed, fast, slow);
#elif defined(USE_WIN32_THREADS)
return rthreads_win32_core_topology(fast, slow);
#elif defined(__APPLE__)
/* macOS 12 / iOS 15 publish the per-level physical counts on Apple
* silicon (level 0 is the performance cluster); older systems and
* Intel Macs have only the total, and every core is fast. */
int p = 0, e = 0, t = 0;
size_t l = sizeof(int);
if ( sysctlbyname("hw.perflevel0.physicalcpu", &p, &l, NULL, 0) == 0
&& p > 0)
{
l = sizeof(int);
if (sysctlbyname("hw.perflevel1.physicalcpu", &e, &l, NULL, 0) != 0)
e = 0;
*fast = (unsigned)p;
*slow = (unsigned)(e > 0 ? e : 0);
return true;
}
l = sizeof(int);
if (sysctlbyname("hw.physicalcpu", &t, &l, NULL, 0) == 0 && t > 0)
{
*fast = (unsigned)t;
*slow = 0;
return true;
}
return false;
#else
(void)fast;
(void)slow;
return false;
#endif
}
bool sthread_prefer_fast_cores(void)
{
#if defined(RTHREADS_HAVE_AFFINITY)
if (!rthreads_fast_state)
rthreads_find_fast_cores();
if (rthreads_fast_state >= 0)
return syscall(__NR_sched_setaffinity, 0,
sizeof(rthreads_fast_mask), rthreads_fast_mask) == 0;
#elif defined(USE_WIN32_THREADS)
if (!rthreads_fast_state)
rthreads_find_fast_cores();
if (rthreads_fast_state >= 0)
return rthreads_set_cpusets(GetCurrentThread(),
rthreads_fast_ids, rthreads_fast_count) != 0;
#elif defined(RTHREADS_HAVE_QOS_OVERRIDE)
/* Apple silicon offers no affinity; quality of service is what
* steers a thread onto the performance cores. */
return pthread_set_qos_class_self_np(QOS_CLASS_USER_INTERACTIVE, 0) == 0;
#elif defined(USE_WIIU_THREADS)
/* Three identical cores, but core 1 carries a 2 MiB L2 against
* 512 KiB on the others, which is why Cafe OS keeps the main
* application thread there. */
return OSSetThreadAffinity(OSGetCurrentThread(),
OS_THREAD_ATTRIB_AFFINITY_CPU1) != FALSE;
#endif
return false;
}
bool sthread_raise_current_priority(void)
{
#if defined(USE_WIN32_THREADS)
return SetThreadPriority(GetCurrentThread(),
THREAD_PRIORITY_TIME_CRITICAL) != 0;
#elif defined(USE_GX_THREADS)
/* Into the upper quarter of the band: above the workers this file
* creates, below anything the system parks at the very top. */
LWP_SetThreadPriority(LWP_GetSelf(), 100);
return true;
#elif defined(USE_CTR_THREADS)
{
s32 prio = 0x30;
svcGetThreadPriority(&prio, CUR_THREAD_HANDLE);
prio -= 2;
if (prio < 0x18)
prio = 0x18;
return svcSetThreadPriority(CUR_THREAD_HANDLE, prio) == 0;
}
#elif defined(USE_PSP_THREADS)
{
int prio = sceKernelGetThreadCurrentPriority() - 4;
if (prio < 0x10)
prio = 0x10;
return sceKernelChangeThreadPriority(
sceKernelGetThreadId(), prio) == 0;
}
#elif defined(USE_PS2_THREADS)
{
ee_thread_status_t st;
int prio;
memset(&st, 0, sizeof(st));
ReferThreadStatus(GetThreadId(), &st);
prio = st.current_priority - 4;
if (prio < 0)
prio = 0;
return ChangeThreadPriority(GetThreadId(), prio) >= 0;
}
#elif defined(USE_SWITCH_THREADS)
{
s32 prio = 0x2C;
svcGetThreadPriority(&prio, CUR_THREAD_HANDLE);
prio -= 2;
if (prio < 0x1C)
prio = 0x1C;
return R_SUCCEEDED(svcSetThreadPriority(CUR_THREAD_HANDLE, prio));
}
#elif defined(USE_WIIU_THREADS)
{
OSThread *self = OSGetCurrentThread();
int32_t prio = OSGetThreadPriority(self) - 4;
if (prio < 0)
prio = 0;
return OSSetThreadPriority(self, prio) != FALSE;
}
#elif defined(USE_VITA_THREADS)
{
int prio = sceKernelGetThreadCurrentPriority() - 8;
if (prio < 64)
prio = 64;
return sceKernelChangeThreadPriority(
sceKernelGetThreadId(), prio) == 0;
}
#elif defined(USE_PS3_THREADS)
{
sys_ppu_thread_t self = 0;
int prio = 1000;
rthreads_ps3_thread_get_id(&self);
rthreads_ps3_thread_get_prio(self, &prio);
prio -= 128;
if (prio < 0)
prio = 0;
return rthreads_ps3_thread_set_prio(self, prio) == 0;
}
#elif defined(__ANDROID__)
/* Bionic lets an app move its own threads into the audio band
* without privilege; -16 is ANDROID_PRIORITY_AUDIO. */
return setpriority(PRIO_PROCESS, gettid(), -16) == 0;
#elif defined(__MACH__) && defined(__APPLE__)
/* The time-constraint policy is what makes a thread real-time on
* Darwin: pthread_setschedparam() only moves it within the
* time-shared band. A period close to a small audio IO cycle with
* a fraction of it as the budget is what the HAL's own IO thread
* runs under; a thread that overruns its budget is demoted by the
* kernel, not killed. Needs no privilege. */
{
struct thread_time_constraint_policy policy;
mach_timebase_info_data_t tb;
double ns_per_tick;
if (mach_timebase_info(&tb) != KERN_SUCCESS || !tb.denom)
return false;
ns_per_tick = (double)tb.numer / (double)tb.denom;
policy.period = (uint32_t)(2900000.0 / ns_per_tick);
policy.computation = (uint32_t)( 750000.0 / ns_per_tick);
policy.constraint = (uint32_t)(2900000.0 / ns_per_tick);
policy.preemptible = TRUE;
return thread_policy_set(pthread_mach_thread_np(pthread_self()),
THREAD_TIME_CONSTRAINT_POLICY, (thread_policy_t)&policy,
THREAD_TIME_CONSTRAINT_POLICY_COUNT) == KERN_SUCCESS;
}
#elif defined(RTHREADS_HAVE_SCHEDPARAM)
/* Real-time round-robin at a middling priority: above every
* time-shared thread, below anything the system runs at the top of
* the band. Distributions that grant the audio group an rtprio
* limit allow this without root. Linux then falls back to what the
* process's rlimits allow; where nothing is granted the thread
* simply keeps its default, which is the caller's contract. */
{
struct sched_param sp;
int lo = sched_get_priority_min(SCHED_RR);
int hi = sched_get_priority_max(SCHED_RR);
#if defined(RTHREADS_HAVE_PRIO_RLIMITS)
struct rlimit rl;
int nice_floor;
int nice_now;
#endif
memset(&sp, 0, sizeof(sp));
if (lo >= 0 && hi >= lo)
{
sp.sched_priority = lo + (hi - lo) / 2;
if (pthread_setschedparam(pthread_self(), SCHED_RR, &sp) == 0)
return true;
#if defined(RTHREADS_HAVE_PRIO_RLIMITS)
/* Without CAP_SYS_NICE a thread may take any real-time
* priority up to its RLIMIT_RTPRIO, so a limit below the
* middle of the band is asked for as it stands. */
if ( getrlimit(RLIMIT_RTPRIO, &rl) == 0
&& rl.rlim_cur != RLIM_INFINITY
&& rl.rlim_cur >= (rlim_t)lo
&& rl.rlim_cur < (rlim_t)sp.sched_priority)
{
sp.sched_priority = (int)rl.rlim_cur;
if (pthread_setschedparam(pthread_self(), SCHED_RR, &sp) == 0)
return true;
}
#endif
}
#if defined(RTHREADS_HAVE_PRIO_RLIMITS)
/* With real time refused, a thread may still lower its own nice
* value as far as 20 - RLIMIT_NICE; on Linux the nice value
* belongs to the thread, named by its tid. -11 is the level a
* sound server takes when it cannot have real time. */
if (getrlimit(RLIMIT_NICE, &rl) != 0 || rl.rlim_cur == 0)
return false;
nice_floor = -11;
if (rl.rlim_cur != RLIM_INFINITY && rl.rlim_cur < 31)
nice_floor = 20 - (int)rl.rlim_cur;
errno = 0;
nice_now = getpriority(PRIO_PROCESS,
(id_t)syscall(__NR_gettid));
if (nice_now == -1 && errno)
return false;
if (nice_floor >= nice_now)
return false;
return setpriority(PRIO_PROCESS,
(id_t)syscall(__NR_gettid), nice_floor) == 0;
#endif
}
#endif
return false;
}
void sthread_setname(const char *name)
{
#if defined(__linux__) && !defined(USE_WIN32_THREADS)
/* prctl rather than pthread_setname_np: it is available on every
* bionic and glibc version we build against, and takes the name as
* a plain buffer, so the caller cannot be rejected outright for
* overrunning the kernel's 16-byte limit. Copied rather than passed
* through so an over-long name truncates instead of failing. */
char buf[16];
size_t i;
if (!name)
return;
for (i = 0; i < sizeof(buf) - 1 && name[i]; i++)
buf[i] = name[i];
buf[i] = '\0';
prctl(PR_SET_NAME, buf, 0, 0, 0);
#elif defined(__APPLE__)
/* pthread_setname_np is 10.6; it is looked up at run time so one
* binary builds against, and runs on, 10.5 as well. The pointer is
* kept once found: it cannot come and go while the process runs. */
static int (*setname)(const char*) = NULL;
static int looked_up = 0;
if (!name)
return;
if (!looked_up)
{
*(void**)&setname = dlsym(RTLD_DEFAULT, "pthread_setname_np");
looked_up = 1;
}
if (setname)
setname(name);
#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
if (!name)
return;
pthread_set_name_np(pthread_self(), name);
#elif defined(__NetBSD__)
if (!name)
return;
pthread_setname_np(pthread_self(), "%s", (void*)name);
#else
(void)name;
#endif
}
int sthread_detach(sthread_t *thread)
{
#if defined(USE_WIN32_THREADS)
if (!thread)
return 0;
CloseHandle(thread->thread);
free(thread);
return 0;
#elif defined(USE_GX_THREADS)
/* LWP has no detach: a thread that is never joined keeps its
* control block and stack allocated for the life of the process.
* Long-lived workers are unaffected; anything churning through
* short-lived threads should join them on this platform. */
if (!thread)
return 0;
free(thread);
return 0;
#elif defined(USE_CTR_THREADS)
if (!thread)
return 0;
threadDetach(thread->id);
free(thread);
return 0;
#elif defined(USE_PSP_THREADS)
if (!thread)
return 0;
if (retro_atomic_fetch_or_int(&thread->start->state,
PSP_THREAD_DETACHED) & PSP_THREAD_DONE)
{
/* Already finished: it is dormant (or about to be) and can no
* longer reap itself, so reap it here. */
sceKernelWaitThreadEnd(thread->id, NULL);
sceKernelDeleteThread(thread->id);
free(thread->start);
}
free(thread);
return 0;
#elif defined(USE_VITA_THREADS)
if (!thread)
return 0;
if (retro_atomic_fetch_or_int(&thread->start->state,
PSP_THREAD_DETACHED) & PSP_THREAD_DONE)
{
/* Already finished: it is dormant (or about to be) and can no
* longer reap itself, so reap it here. */
sceKernelWaitThreadEnd(thread->id, NULL, NULL);
sceKernelDeleteThread(thread->id);
free(thread->start);
}
free(thread);
return 0;
#elif defined(USE_WIIU_THREADS)
if (!thread)
return 0;
/* The deallocator runs once the thread exits and frees its block. */
OSDetachThread(thread->id);
free(thread);
return 0;
#elif defined(USE_PS2_THREADS)
if (!thread)
return 0;
ps2_reap();
if (retro_atomic_fetch_or_int(&thread->state, PS2_THREAD_DETACHED)
& PS2_THREAD_DONE)
{
/* Already exited: nothing left running on that stack. */
DeleteSema(thread->done);
free(thread->stack);
free(thread);
return 0;
}
DIntr();
thread->next = ps2_reap_list;
ps2_reap_list = thread;
EIntr();
return 0;
#elif defined(USE_SWITCH_THREADS)
if (!thread)
return 0;
switch_reap();
mutexLock(&switch_reap_lock);
thread->next = switch_reap_list;
switch_reap_list = thread;
mutexUnlock(&switch_reap_lock);
return 0;
#elif defined(USE_PS3_THREADS)
{
int ret;
if (!thread)
return 0;
ret = rthreads_ps3_thread_detach(thread->id);
free(thread);
return ret;
}
#else
int ret;
if (!thread)
return 0;
ret = pthread_detach(thread->id);
free(thread);
return ret;
#endif
}
void sthread_join(sthread_t *thread)
{
if (!thread)
return;
#if defined(USE_WIN32_THREADS)
WaitForSingleObject(thread->thread, INFINITE);
CloseHandle(thread->thread);
#elif defined(USE_GX_THREADS)
LWP_JoinThread(thread->id, NULL);
#elif defined(USE_CTR_THREADS)
threadJoin(thread->id, UINT64_MAX);
threadFree(thread->id);
#elif defined(USE_PSP_THREADS)
sceKernelWaitThreadEnd(thread->id, NULL);
sceKernelDeleteThread(thread->id);
free(thread->start);
#elif defined(USE_VITA_THREADS)
sceKernelWaitThreadEnd(thread->id, NULL, NULL);
sceKernelDeleteThread(thread->id);
free(thread->start);
#elif defined(USE_WIIU_THREADS)
/* The deallocator frees the control block and stack. */
OSJoinThread(thread->id, NULL);
#elif defined(USE_SWITCH_THREADS)
threadWaitForExit(&thread->t);
threadClose(&thread->t);
#elif defined(USE_PS2_THREADS)
WaitSema(thread->done);
DeleteSema(thread->done);
free(thread->stack);
#elif defined(USE_PS3_THREADS)
{
uint64_t exitcode = 0;
rthreads_ps3_thread_join(thread->id, &exitcode);
}
#else
pthread_join(thread->id, NULL);
#endif
free(thread);
}
/* Affinity. A mask of 0 means "any CPU", which is how a caller undoes a
* pin. Windows takes the mask directly; glibc takes a cpu_set built from
* it; Bionic has no pthread_setaffinity_np, so the thread's kernel id is
* used with sched_setaffinity instead. Darwin has no hard affinity, only
* a scheduler hint, and reports false rather than pretend; so does every
* console and every other backend. */
#if defined(USE_WIN32_THREADS)
static bool sthread_set_affinity_handle(HANDLE h, uint64_t mask)
{
if (mask == 0)
mask = ~mask;
return SetThreadAffinityMask(h, (DWORD_PTR)mask) != 0;
}
#elif defined(__ANDROID__)
/* bionic declares cpu_set_t and sched_setaffinity under _GNU_SOURCE
* only, which has to be set before the first system header - not
* something a unity build's include order provides. The kernel call
* takes a plain bit mask, so it is made directly. */
static bool sthread_set_tid_affinity(pid_t tid, uint64_t mask)
{
unsigned long bits[64 / (8 * sizeof(unsigned long))];
unsigned i;
memset(bits, 0, sizeof(bits));
if (!mask)
{
long n = sysconf(_SC_NPROCESSORS_CONF);
for (i = 0; (long)i < n && i < 64; i++)
mask |= (uint64_t)1 << i;
}
for (i = 0; i < 64; i++)
if (mask & ((uint64_t)1 << i))
bits[i / (8 * sizeof(unsigned long))] |=
1UL << (i % (8 * sizeof(unsigned long)));
return syscall(__NR_sched_setaffinity, tid, sizeof(bits), bits) == 0;
}
#elif defined(__linux__) && !defined(USE_GX_THREADS) && !defined(USE_CTR_THREADS) \
&& !defined(USE_PSP_THREADS) && !defined(USE_PS2_THREADS) && !defined(USE_PS3_THREADS) \
&& !defined(USE_SWITCH_THREADS) && !defined(USE_WIIU_THREADS) && !defined(USE_VITA_THREADS)
#include <sched.h>
static void sthread_mask_to_set(uint64_t mask, cpu_set_t *set)
{
unsigned i;
CPU_ZERO(set);
if (mask)
{
for (i = 0; i < 64; i++)
if (mask & ((uint64_t)1 << i))
CPU_SET(i, set);
}
else
{
long n = sysconf(_SC_NPROCESSORS_CONF);
for (i = 0; (long)i < n && i < CPU_SETSIZE; i++)
CPU_SET(i, set);
}
}
#endif
bool sthread_set_affinity(sthread_t *thread, uint64_t mask)
{
#if defined(USE_WIN32_THREADS)
return thread && sthread_set_affinity_handle(thread->thread, mask);
#elif defined(__ANDROID__)
#if __ANDROID_API__ >= 21
return thread && sthread_set_tid_affinity(pthread_gettid_np(thread->id), mask);
#else
/* No pthread_gettid_np before API 21 and no other way to name
* another thread to sched_setaffinity; only the calling thread can
* pin itself there (sthread_set_current_affinity). */
(void)thread; (void)mask;
return false;
#endif
#elif defined(__linux__) && !defined(USE_GX_THREADS) && !defined(USE_CTR_THREADS) \
&& !defined(USE_PSP_THREADS) && !defined(USE_PS2_THREADS) && !defined(USE_PS3_THREADS) \
&& !defined(USE_SWITCH_THREADS) && !defined(USE_WIIU_THREADS) && !defined(USE_VITA_THREADS)
cpu_set_t set;
if (thread)
{
sthread_mask_to_set(mask, &set);
return pthread_setaffinity_np(thread->id, sizeof(set), &set) == 0;
}
#else
(void)thread; (void)mask;
#endif
return false;
}
bool sthread_set_current_affinity(uint64_t mask)
{
#if defined(USE_WIN32_THREADS)
return sthread_set_affinity_handle(GetCurrentThread(), mask);
#elif defined(__ANDROID__)
return sthread_set_tid_affinity(0, mask);
#elif defined(__linux__) && !defined(USE_GX_THREADS) && !defined(USE_CTR_THREADS) \
&& !defined(USE_PSP_THREADS) && !defined(USE_PS2_THREADS) && !defined(USE_PS3_THREADS) \
&& !defined(USE_SWITCH_THREADS) && !defined(USE_WIIU_THREADS) && !defined(USE_VITA_THREADS)
cpu_set_t set;
sthread_mask_to_set(mask, &set);
return sched_setaffinity(0, sizeof(set), &set) == 0;
#else
(void)mask;
return false;
#endif
}
/* Yield the rest of this timeslice to any runnable thread. What a
* bounded spin does when its bound is reached and it is not yet ready to
* park: the signal-safe alternative to a lock, for the one place a lock
* cannot go (a fault handler), and the backoff between polls of a
* condition that has no waiter list. Every backend has one. */
void sthread_yield(void)
{
#if defined(USE_WIN32_THREADS)
SwitchToThread();
#elif defined(USE_GX_THREADS)
LWP_YieldThread();
#elif defined(USE_CTR_THREADS)
svcSleepThread(0);
#elif defined(USE_PSP_THREADS) || defined(USE_VITA_THREADS)
sceKernelDelayThread(0);
#elif defined(USE_WIIU_THREADS)
OSYieldThread();
#elif defined(USE_SWITCH_THREADS)
svcSleepThread(0);
#elif defined(USE_PS2_THREADS)
RotateThreadReadyQueue(0);
#elif defined(USE_PS3_THREADS)
rthreads_ps3_thread_yield();
#elif defined(USE_GEKKO_THREADS)
gk_thread_yield();
#else
sched_yield();
#endif
}
bool sthread_isself(sthread_t *thread)
{
#if defined(USE_WIN32_THREADS)
return thread ? GetCurrentThreadId() == thread->id : false;
#elif defined(USE_GX_THREADS)
return thread ? LWP_GetSelf() == thread->id : false;
#elif defined(USE_CTR_THREADS)
return thread ? threadGetCurrent() == thread->id : false;
#elif defined(RTHREADS_SCE_START)
return thread ? sceKernelGetThreadId() == thread->id : false;
#elif defined(USE_WIIU_THREADS)
return thread ? OSGetCurrentThread() == thread->id : false;
#elif defined(USE_SWITCH_THREADS)
return thread ? threadGetSelf() == &thread->t : false;
#elif defined(USE_PS2_THREADS)
return thread ? GetThreadId() == thread->id : false;
#elif defined(USE_PS3_THREADS)
{
sys_ppu_thread_t self = 0;
if (!thread)
return false;
rthreads_ps3_thread_get_id(&self);
return self == thread->id;
}
#else
return thread ? pthread_equal(pthread_self(), thread->id) : false;
#endif
}
slock_t *slock_new(void)
{
slock_t *lock = (slock_t*)calloc(1, sizeof(*lock));
if (!lock)
return NULL;
#if defined(USE_WIN32_THREADS)
InitializeCriticalSection(&lock->lock);
#elif defined(USE_GX_THREADS)
if (LWP_MutexInit(&lock->lock, false) != 0)
{
free(lock);
return NULL;
}
#elif defined(USE_CTR_THREADS)
LightLock_Init(&lock->lock);
#elif defined(USE_PSP_THREADS)
if ((lock->lock = sceKernelCreateSema("rarch_lock", 0, 1, 1, NULL)) < 0)
{
free(lock);
return NULL;
}
#elif defined(USE_VITA_THREADS)
if (sceKernelCreateLwMutex(&lock->lock, "rarch_lock", 0, 0, NULL) < 0)
{
free(lock);
return NULL;
}
#elif defined(USE_PS3_THREADS)
{
rthreads_ps3_lwmutex_attr_t attr;
RTHREADS_PS3_LWMUTEX_ATTR_INIT(attr);
if (rthreads_ps3_lwmutex_create(&lock->lock, &attr) != 0)
{
free(lock);
return NULL;
}
}
#elif defined(USE_WIIU_THREADS)
OSFastMutex_Init(&lock->lock, "rarch_lock");
#elif defined(USE_SWITCH_THREADS)
mutexInit(&lock->lock);
#elif defined(USE_PS2_THREADS)
{
ee_sema_t sema;
memset(&sema, 0, sizeof(sema));
sema.init_count = 1;
sema.max_count = 1;
if ((lock->lock = CreateSema(&sema)) < 0)
{
free(lock);
return NULL;
}
}
#else
if (pthread_mutex_init(&lock->lock, NULL) != 0)
{
free(lock);
return NULL;
}
#endif
return lock;
}
void slock_free(slock_t *lock)
{
if (!lock)
return;
#if defined(USE_WIN32_THREADS)
DeleteCriticalSection(&lock->lock);
#elif defined(USE_GX_THREADS)
LWP_MutexDestroy(lock->lock);
#elif defined(USE_CTR_THREADS)
/* nothing to destroy */
#elif defined(USE_PSP_THREADS)
sceKernelDeleteSema(lock->lock);
#elif defined(USE_VITA_THREADS)
sceKernelDeleteLwMutex(&lock->lock);
#elif defined(USE_WIIU_THREADS) || defined(USE_SWITCH_THREADS)
/* nothing to destroy */
#elif defined(USE_PS2_THREADS)
DeleteSema(lock->lock);
#elif defined(USE_PS3_THREADS)
rthreads_ps3_lwmutex_destroy(&lock->lock);
#else
pthread_mutex_destroy(&lock->lock);
#endif
free(lock);
}
void slock_lock(slock_t *lock)
{
if (!lock)
return;
#if defined(USE_WIN32_THREADS)
EnterCriticalSection(&lock->lock);
#elif defined(USE_GX_THREADS)
LWP_MutexLock(lock->lock);
#elif defined(USE_CTR_THREADS)
LightLock_Lock(&lock->lock);
#elif defined(USE_PSP_THREADS)
sceKernelWaitSema(lock->lock, 1, NULL);
#elif defined(USE_VITA_THREADS)
sceKernelLockLwMutex(&lock->lock, 1, NULL);
#elif defined(USE_WIIU_THREADS)
OSFastMutex_Lock(&lock->lock);
#elif defined(USE_SWITCH_THREADS)
mutexLock(&lock->lock);
#elif defined(USE_PS2_THREADS)
WaitSema(lock->lock);
#elif defined(USE_PS3_THREADS)
rthreads_ps3_lwmutex_lock(&lock->lock, RTHREADS_PS3_NO_TIMEOUT);
#else
pthread_mutex_lock(&lock->lock);
#endif
}
bool slock_try_lock(slock_t *lock)
{
#if defined(USE_WIN32_THREADS)
return lock && TryEnterCriticalSection(&lock->lock);
#elif defined(USE_GX_THREADS)
return lock && (LWP_MutexTryLock(lock->lock) == 0);
#elif defined(USE_CTR_THREADS)
return lock && (LightLock_TryLock(&lock->lock) == 0);
#elif defined(USE_PSP_THREADS)
return lock && (sceKernelPollSema(lock->lock, 1) == 0);
#elif defined(USE_VITA_THREADS)
return lock && (sceKernelTryLockLwMutex(&lock->lock, 1) == 0);
#elif defined(USE_WIIU_THREADS)
return lock && OSFastMutex_TryLock(&lock->lock);
#elif defined(USE_SWITCH_THREADS)
return lock && mutexTryLock(&lock->lock);
#elif defined(USE_PS2_THREADS)
return lock && (PollSema(lock->lock) >= 0);
#elif defined(USE_PS3_THREADS)
return lock && (rthreads_ps3_lwmutex_trylock(&lock->lock) == 0);
#else
return lock && (pthread_mutex_trylock(&lock->lock) == 0);
#endif
}
void slock_unlock(slock_t *lock)
{
if (!lock)
return;
#if defined(USE_WIN32_THREADS)
LeaveCriticalSection(&lock->lock);
#elif defined(USE_GX_THREADS)
LWP_MutexUnlock(lock->lock);
#elif defined(USE_CTR_THREADS)
LightLock_Unlock(&lock->lock);
#elif defined(USE_PSP_THREADS)
sceKernelSignalSema(lock->lock, 1);
#elif defined(USE_VITA_THREADS)
sceKernelUnlockLwMutex(&lock->lock, 1);
#elif defined(USE_WIIU_THREADS)
OSFastMutex_Unlock(&lock->lock);
#elif defined(USE_SWITCH_THREADS)
mutexUnlock(&lock->lock);
#elif defined(USE_PS2_THREADS)
SignalSema(lock->lock);
#elif defined(USE_PS3_THREADS)
rthreads_ps3_lwmutex_unlock(&lock->lock);
#else
pthread_mutex_unlock(&lock->lock);
#endif
}
#ifdef USE_VITA_THREADS
/* A LwCond is created bound to one LwMutex, and scond only learns its
* lock at the first wait, so binding happens there. The caller holds
* the lock at that point, so concurrent first waits are serialized by
* the lock itself; a signal that still observes the condvar as
* unbound corresponds to a moment with no blocked waiter, where
* dropping the signal is what a condvar does anyway. Waiting on one
* condvar with two different locks is as undefined here as it is for
* pthreads. */
static void scond_bind_vita(scond_t *cond, slock_t *lock)
{
if (!retro_atomic_load_acquire_int(&cond->bound))
{
cond->assoc = &lock->lock;
sceKernelCreateLwCond(&cond->work, "rarch_cond", 0,
&lock->lock, NULL);
retro_atomic_store_release_int(&cond->bound, 1);
}
#ifdef DEBUG
retro_assert(cond->assoc == &lock->lock);
#endif
}
#endif
#ifdef USE_PS3_THREADS
/* An lwcond is created bound to one lwmutex, and scond only learns its
* lock at the first wait, so binding happens there. The caller holds
* the lock at that point, so concurrent first waits are serialized by
* the lock itself; a signal that still observes the condvar as unbound
* corresponds to a moment with no blocked waiter, where dropping the
* signal is what a condvar does anyway. Waiting on one condvar with
* two different locks is as undefined here as it is for pthreads. */
static void scond_bind_ps3(scond_t *cond, slock_t *lock)
{
if (!retro_atomic_load_acquire_int(&cond->bound))
{
rthreads_ps3_lwcond_attr_t attr;
RTHREADS_PS3_LWCOND_ATTR_INIT(attr);
cond->assoc = &lock->lock;
rthreads_ps3_lwcond_create(&cond->work, &lock->lock, &attr);
retro_atomic_store_release_int(&cond->bound, 1);
}
#ifdef DEBUG
retro_assert(cond->assoc == &lock->lock);
#endif
}
#endif
scond_t *scond_new(void)
{
scond_t *cond = (scond_t*)calloc(1, sizeof(*cond));
if (!cond)
return NULL;
#if defined(USE_WIN32_THREADS)
scond_global_init();
retro_atomic_ptr_init(&cond->head, NULL);
#elif defined(USE_GX_THREADS)
if (LWP_CondInit(&cond->cond) != 0)
{
free(cond);
return NULL;
}
#elif defined(USE_CTR_THREADS)
CondVar_Init(&cond->cond);
#elif defined(USE_PSP_THREADS)
cond->waiters = 0;
cond->gate = sceKernelCreateSema("rarch_cond_gate", 0, 1, 1, NULL);
cond->sema = sceKernelCreateSema("rarch_cond", 0, 0, 0x7FFFFFFF, NULL);
if (cond->gate < 0 || cond->sema < 0)
{
if (cond->gate >= 0)
sceKernelDeleteSema(cond->gate);
if (cond->sema >= 0)
sceKernelDeleteSema(cond->sema);
free(cond);
return NULL;
}
#elif defined(USE_VITA_THREADS)
cond->assoc = NULL;
retro_atomic_int_init(&cond->bound, 0);
#elif defined(USE_PS3_THREADS)
cond->assoc = NULL;
retro_atomic_int_init(&cond->bound, 0);
#elif defined(USE_WIIU_THREADS)
OSFastMutex_Init(&cond->gate, "rarch_cond");
#elif defined(USE_SWITCH_THREADS)
condvarInit(&cond->cond);
#elif defined(USE_PS2_THREADS)
/* head and tail start NULL from calloc */
#elif defined(RTHREADS_FUTEX_SCOND)
retro_atomic_int_init(&cond->seq, 0);
retro_atomic_int_init(&cond->waiters, 0);
#else
if (pthread_cond_init(&cond->cond, NULL) != 0)
{
free(cond);
return NULL;
}
#endif
return cond;
}
void scond_free(scond_t *cond)
{
if (!cond)
return;
#if defined(USE_WIN32_THREADS)
/* nothing is owned: the waiter blocks live on their threads' stacks */
#elif defined(USE_GX_THREADS)
LWP_CondDestroy(cond->cond);
#elif defined(USE_CTR_THREADS)
/* nothing to destroy */
#elif defined(USE_PSP_THREADS)
sceKernelDeleteSema(cond->sema);
sceKernelDeleteSema(cond->gate);
#elif defined(USE_VITA_THREADS)
if (retro_atomic_load_acquire_int(&cond->bound))
sceKernelDeleteLwCond(&cond->work);
#elif defined(USE_PS3_THREADS)
if (retro_atomic_load_acquire_int(&cond->bound))
rthreads_ps3_lwcond_destroy(&cond->work);
#elif defined(USE_WIIU_THREADS)
/* nothing to destroy: waiter nodes live on their threads' stacks */
#elif defined(USE_SWITCH_THREADS) || defined(USE_PS2_THREADS) \
|| defined(RTHREADS_FUTEX_SCOND)
/* nothing to destroy */
#else
pthread_cond_destroy(&cond->cond);
#endif
free(cond);
}
#ifdef USE_WIN32_THREADS
/* ---- processor probes ---------------------------------------------- */
#if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64)) && _MSC_VER >= 1500
#include <intrin.h>
#define SCOND_HAVE_X86_INTRIN 1
#if _MSC_VER >= 1912
#include <immintrin.h>
#define SCOND_HAVE_MWAITX 1
#endif
#if _MSC_VER >= 1924
#define SCOND_HAVE_UMWAIT 1
#endif
#elif defined(__GNUC__) && defined(__x86_64__)
#define SCOND_HAVE_X86_ASM 1
#define SCOND_HAVE_MWAITX 1
#define SCOND_HAVE_UMWAIT 1
#endif
#if defined(SCOND_HAVE_X86_INTRIN) || defined(SCOND_HAVE_X86_ASM)
#define SCOND_HAVE_X86 1
#endif
#if defined(SCOND_HAVE_X86)
static void scond_cpuid(unsigned leaf, unsigned sub, unsigned r[4])
{
#if defined(SCOND_HAVE_X86_INTRIN)
int v[4];
__cpuidex(v, (int)leaf, (int)sub);
r[0] = (unsigned)v[0]; r[1] = (unsigned)v[1];
r[2] = (unsigned)v[2]; r[3] = (unsigned)v[3];
#else
__asm__ __volatile__("cpuid"
: "=a"(r[0]), "=b"(r[1]), "=c"(r[2]), "=d"(r[3])
: "a"(leaf), "c"(sub));
#endif
}
static INLINE unsigned __int64 scond_tsc(void)
{
#if defined(SCOND_HAVE_X86_INTRIN)
return __rdtsc();
#else
unsigned lo, hi;
__asm__ __volatile__("rdtsc" : "=a"(lo), "=d"(hi));
return ((unsigned __int64)hi << 32) | lo;
#endif
}
static INLINE void scond_pause(void)
{
#if defined(SCOND_HAVE_X86_INTRIN)
_mm_pause();
#else
__asm__ __volatile__("pause" ::: "memory");
#endif
}
#if defined(SCOND_HAVE_MWAITX)
/* AMD: park on the line holding *addr until it is written or ticks
* TSC cycles pass (ECX bit 1 enables the timer) */
static INLINE void scond_monitorx(const volatile void *addr)
{
#if defined(SCOND_HAVE_X86_INTRIN)
_mm_monitorx((void*)addr, 0, 0);
#else
__asm__ __volatile__(".byte 0x0f, 0x01, 0xfa" /* monitorx */
:: "a"(addr), "c"(0), "d"(0) : "memory");
#endif
}
static INLINE void scond_mwaitx(unsigned ticks)
{
#if defined(SCOND_HAVE_X86_INTRIN)
_mm_mwaitx(0, 2, ticks);
#else
__asm__ __volatile__(".byte 0x0f, 0x01, 0xfb" /* mwaitx */
:: "a"(0), "b"(ticks), "c"(2) : "memory");
#endif
}
#endif
#if defined(SCOND_HAVE_UMWAIT)
/* Intel WAITPKG: park on the line until written or the absolute TSC
* deadline; control 1 asks for the lighter C0.1 state */
static INLINE void scond_umonitor(const volatile void *addr)
{
#if defined(SCOND_HAVE_X86_INTRIN)
_umonitor((void*)addr);
#else
__asm__ __volatile__(".byte 0xf3, 0x0f, 0xae, 0xf7" /* umonitor rdi */
:: "D"(addr) : "memory");
#endif
}
static INLINE void scond_umwait(unsigned __int64 deadline)
{
#if defined(SCOND_HAVE_X86_INTRIN)
_umwait(1, deadline);
#else
__asm__ __volatile__(".byte 0xf2, 0x0f, 0xae, 0xf1" /* umwait ecx */
:: "c"(1), "a"((unsigned)deadline), "d"((unsigned)(deadline >> 32))
: "memory", "cc");
#endif
}
#endif
#endif /* SCOND_HAVE_X86 */
/* the spin polls the waiter's own flag word: a plain load on x86 (the
* word is written by one waker with a locked op), an acquire elsewhere.
*
* The plain load has to be spelled per backend rather than by casting the
* field. retro_atomic_int_t is a real atomic type on the C11, C++11 and
* __atomic backends -- mingw picks C11, so the field is an atomic_int --
* and reading one through a volatile LONG lvalue is a type pun that both
* breaks strict aliasing and is undefined besides. Those three backends
* go through the API instead, which costs nothing here: their acquire
* load is a plain MOV on x86. The MSVC, Apple, __sync and volatile
* backends type the field as a plain volatile integer, so it can just be
* read -- same type, no cast -- and that matters for them, because their
* acquire load is a locked RMW (InterlockedCompareExchangeAcquire,
* OSAtomicAdd32Barrier, __sync_fetch_and_add(p, 0)), far too heavy to run
* every iteration against a line one waker writes. */
#if defined(SCOND_HAVE_X86) && (defined(RETRO_ATOMIC_BACKEND_MSVC) \
|| defined(RETRO_ATOMIC_BACKEND_APPLE) \
|| defined(RETRO_ATOMIC_BACKEND_SYNC) \
|| defined(RETRO_ATOMIC_BACKEND_VOLATILE))
#define scond_flags_peek(w) ((w)->flags)
#else
#define scond_flags_peek(w) retro_atomic_load_acquire_int(&(w)->flags)
#endif
/* ---- once-per-process setup ---------------------------------------- */
/* TSC cycles a waiter spins on its flag word before blocking. Measured
* on a 9950X3D at 600 dispatches a frame: below about 32k the spin no
* longer bridges the interval between dispatches and every one of them
* pays a kernel wait, while above 64k nothing is gained at four or
* eight threads and an oversubscribed pool loses steadily, its spinners
* taking cycles from the sibling threads doing the work. ntdll spins
* 0x90b40, an order of magnitude longer, which suits a general-purpose
* wait rather than one dispatch arriving every few microseconds. */
#define SCOND_DEFAULT_SPIN_CYCLES 64000u
static void scond_global_resolve(void)
{
#if !defined(_XBOX)
SYSTEM_INFO si;
#endif
const char *env;
unsigned cycles = SCOND_DEFAULT_SPIN_CYCLES;
scond_g.sleep = SCOND_SLEEP_EVENT;
#if !defined(_XBOX)
{
HMODULE nt = GetModuleHandleA("ntdll.dll");
if (nt)
{
scond_nt_create_keyed_t create_keyed;
scond_g.wait_alert = (scond_nt_wait_alert_t)(void (*)(void))
GetProcAddress(nt, "NtWaitForAlertByThreadId");
scond_g.alert_tid = (scond_nt_alert_tid_t)(void (*)(void))
GetProcAddress(nt, "NtAlertThreadByThreadId");
scond_g.wait_keyed = (scond_nt_keyed_t)(void (*)(void))
GetProcAddress(nt, "NtWaitForKeyedEvent");
scond_g.release_keyed = (scond_nt_keyed_t)(void (*)(void))
GetProcAddress(nt, "NtReleaseKeyedEvent");
create_keyed = (scond_nt_create_keyed_t)(void (*)(void))
GetProcAddress(nt, "NtCreateKeyedEvent");
env = getenv("RTHREADS_SCOND");
if (scond_g.wait_alert && scond_g.alert_tid
&& !(env && strcmp(env, "alert")))
scond_g.sleep = SCOND_SLEEP_ALERT;
else if (scond_g.wait_keyed && scond_g.release_keyed && create_keyed
&& !(env && strcmp(env, "keyed"))
&& create_keyed(&scond_g.keyed, 0x1f0003 /* EVENT_ALL_ACCESS */, NULL, 0) == 0)
scond_g.sleep = SCOND_SLEEP_KEYED;
}
}
#endif
if (scond_g.sleep == SCOND_SLEEP_EVENT)
scond_g.tls_event = TlsAlloc();
/* The high resolution timer for bounded waits, on every tier. It
* needs the thread's event to wait on beside it, so that slot is
* taken here on the tiers that do not otherwise use one. Probed
* rather than version-checked: the flag is refused before Windows 10
* 1803. RTHREADS_SCOND_HIRES=0 keeps the kernel timeout alone, for
* measuring against it. */
scond_g.hires = false;
scond_g.tls_timer = TLS_OUT_OF_INDEXES;
#if !defined(_XBOX) && !defined(__WINRT__) && !(defined(WINAPI_FAMILY) && WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP)
env = getenv("RTHREADS_SCOND_HIRES");
if (!(env && !strcmp(env, "0")))
{
HMODULE k32 = GetModuleHandleA("kernel32.dll");
if (k32)
{
scond_g.create_timer_ex = (scond_create_timer_ex_t)(void (*)(void))
GetProcAddress(k32, "CreateWaitableTimerExW");
scond_g.set_timer = (scond_set_timer_t)(void (*)(void))
GetProcAddress(k32, "SetWaitableTimer");
}
if (scond_g.create_timer_ex && scond_g.set_timer)
{
HANDLE probe = scond_g.create_timer_ex(NULL, NULL,
SCOND_TIMER_HIGH_RESOLUTION, SCOND_TIMER_ALL_ACCESS);
if (probe)
{
CloseHandle(probe);
if (scond_g.sleep != SCOND_SLEEP_EVENT)
scond_g.tls_event = TlsAlloc();
scond_g.tls_timer = TlsAlloc();
scond_g.hires = scond_g.tls_event != TLS_OUT_OF_INDEXES
&& scond_g.tls_timer != TLS_OUT_OF_INDEXES;
}
}
}
#endif
#if defined(_XBOX)
/* the 360 has six hardware threads, the original Xbox one core */
#if defined(_M_PPC) || defined(_XENON)
scond_g.spin = SCOND_SPIN_PAUSE;
#else
scond_g.spin = SCOND_SPIN_NONE;
#endif
#else
#if defined(__WINRT__) || defined(WINAPI_FAMILY) && WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP
GetNativeSystemInfo(&si);
#else
GetSystemInfo(&si);
#endif
scond_g.spin = si.dwNumberOfProcessors > 1 ? SCOND_SPIN_PAUSE : SCOND_SPIN_NONE;
#endif
env = getenv("RTHREADS_SCOND_SPIN");
if (env)
{
if (!strcmp(env, "none"))
scond_g.spin = SCOND_SPIN_NONE;
else if (!strcmp(env, "pause"))
scond_g.spin = SCOND_SPIN_PAUSE;
#if defined(SCOND_HAVE_X86)
else if (!strcmp(env, "mwaitx") || !strcmp(env, "umwait"))
{
/* only where the processor has it */
unsigned r[4];
scond_cpuid(0, 0, r);
#if defined(SCOND_HAVE_UMWAIT)
if (env[0] == 'u' && r[0] >= 7)
{
scond_cpuid(7, 0, r);
if (r[2] & (1u << 5))
scond_g.spin = SCOND_SPIN_UMWAIT;
}
#endif
#if defined(SCOND_HAVE_MWAITX)
if (env[0] == 'm')
{
scond_cpuid(0x80000000u, 0, r);
if (r[0] >= 0x80000001u)
{
scond_cpuid(0x80000001u, 0, r);
if (r[2] & (1u << 29))
scond_g.spin = SCOND_SPIN_MWAITX;
}
}
#endif
}
#endif
}
env = getenv("RTHREADS_SCOND_SPIN_CYCLES");
if (env && env[0] >= '0' && env[0] <= '9')
cycles = (unsigned)strtoul(env, NULL, 0);
scond_g.spin_cycles = cycles;
/* a pause is on the order of a hundred cycles */
scond_g.spin_iters = cycles / 128u;
if (!scond_g.spin_iters)
scond_g.spin_iters = 1;
}
static void scond_global_init(void)
{
if (retro_atomic_load_acquire_int(&scond_g.state) == 2)
return;
if (retro_atomic_cas_int(&scond_g.state, 0, 1))
{
scond_global_resolve();
retro_atomic_store_release_int(&scond_g.state, 2);
return;
}
while (retro_atomic_load_acquire_int(&scond_g.state) != 2)
Sleep(0);
}
/* ---- kernel wait and wake ------------------------------------------ */
#define SCOND_STATUS_TIMEOUT 0x102
/* block until woken or the timeout (NULL = never) passes; false on timeout */
static bool scond_sleep(struct scond_waiter *w, LARGE_INTEGER *timeout)
{
switch (scond_g.sleep)
{
case SCOND_SLEEP_ALERT:
return scond_g.wait_alert(&w->flags, timeout) != SCOND_STATUS_TIMEOUT;
case SCOND_SLEEP_KEYED:
return scond_g.wait_keyed(scond_g.keyed, w, FALSE, timeout)
!= SCOND_STATUS_TIMEOUT;
default:
{
DWORD ms = timeout
? (DWORD)((-timeout->QuadPart + 9999) / 10000) : INFINITE;
return WaitForSingleObject(w->event, ms) != WAIT_TIMEOUT;
}
}
}
/* This thread's event and high resolution timer, made on first use and
* kept for the thread's life, as the event tier keeps its event. NULL
* when either cannot be had, and the wait falls back to the kernel
* timeout. */
static HANDLE scond_thread_event(void)
{
HANDLE event = (HANDLE)TlsGetValue(scond_g.tls_event);
if (!event)
{
if (!(event = CreateEvent(NULL, FALSE, FALSE, NULL)))
return NULL;
if (!TlsSetValue(scond_g.tls_event, event))
{
CloseHandle(event);
return NULL;
}
}
return event;
}
static HANDLE scond_thread_timer(void)
{
HANDLE timer = (HANDLE)TlsGetValue(scond_g.tls_timer);
if (!timer)
{
if (!(timer = scond_g.create_timer_ex(NULL, NULL,
SCOND_TIMER_HIGH_RESOLUTION, SCOND_TIMER_ALL_ACCESS)))
return NULL;
if (!TlsSetValue(scond_g.tls_timer, timer))
{
CloseHandle(timer);
return NULL;
}
}
return timer;
}
/* A bounded sleep on the waiter's event and the high resolution timer:
* false when the timer fired first. Setting the timer clears whatever
* an earlier wait left signalled on it. */
static bool scond_sleep_hires(struct scond_waiter *w, HANDLE timer,
int64_t timeout_us)
{
HANDLE handles[2];
LARGE_INTEGER due;
DWORD rc;
due.QuadPart = -(LONGLONG)timeout_us * 10;
if (!scond_g.set_timer(timer, &due, 0, NULL, NULL, FALSE))
{
LONGLONG ms = (timeout_us + 999) / 1000;
rc = WaitForSingleObject(w->event,
ms >= (LONGLONG)INFINITE ? INFINITE - 1 : (DWORD)ms);
return rc != WAIT_TIMEOUT;
}
handles[0] = w->event;
handles[1] = timer;
/* The timer is set and will fire; the bound is only there so a
* timer that somehow does not cannot make this wait forever */
rc = WaitForMultipleObjects(2, handles, FALSE,
(DWORD)(timeout_us / 1000) + 100);
if (rc == WAIT_OBJECT_0)
return true;
if (rc == WAIT_OBJECT_0 + 1 || rc == WAIT_TIMEOUT)
return false;
/* An unusable handle: a wake after a millisecond, the caller
* re-checks its own predicate */
Sleep(1);
return true;
}
static void scond_wake_one(struct scond_waiter *w)
{
/* copies taken first: the waiter may leave as soon as it sees WOKEN */
DWORD tid = w->tid;
HANDLE event = w->event;
int prev = retro_atomic_fetch_or_int(&w->flags, SCOND_W_WOKEN);
if (!(prev & SCOND_W_ASLEEP))
return; /* still spinning: it sees the flag, no syscall */
/* A waiter that sleeps on its event - every waiter on the event
* tier, and bounded ones on the others - is woken through it */
if (event)
{
SetEvent(event);
return;
}
switch (scond_g.sleep)
{
case SCOND_SLEEP_ALERT:
scond_g.alert_tid((HANDLE)(uintptr_t)tid);
break;
case SCOND_SLEEP_KEYED:
scond_g.release_keyed(scond_g.keyed, w, FALSE, NULL);
break;
default:
SetEvent(event);
break;
}
}
/* ---- the list ------------------------------------------------------ */
static INLINE uintptr_t scond_head(scond_t *cond)
{
return (uintptr_t)retro_atomic_load_acquire_ptr(&cond->head);
}
static void scond_lock(scond_t *cond)
{
for (;;)
{
uintptr_t old = scond_head(cond);
if (!(old & SCOND_HEAD_LOCK)
&& retro_atomic_cas_ptr(&cond->head, (void*)old,
(void*)(old | SCOND_HEAD_LOCK)))
return;
#if defined(SCOND_HAVE_X86)
scond_pause();
#else
Sleep(0);
#endif
}
}
static void scond_unlock(scond_t *cond)
{
for (;;)
{
uintptr_t old = scond_head(cond);
if (retro_atomic_cas_ptr(&cond->head, (void*)old,
(void*)(old & SCOND_HEAD_MASK)))
return;
}
}
/* unlink w if it is still on the list; the lock must be held. A block
* that is not there any more has been taken by a waker. */
static bool scond_unlink(scond_t *cond, struct scond_waiter *w)
{
for (;;)
{
uintptr_t old = scond_head(cond);
struct scond_waiter *n = (struct scond_waiter*)(old & SCOND_HEAD_MASK);
if (n == w)
{
if (retro_atomic_cas_ptr(&cond->head, (void*)old,
(void*)((uintptr_t)w->next | SCOND_HEAD_LOCK)))
return true;
continue; /* a push landed in front of it: look again */
}
while (n && n->next != w)
n = n->next;
if (!n)
return false;
n->next = w->next;
return true;
}
}
/* Block on the caller's own flag word until signalled or timeout_us
* have passed, -1 for never. Returns false only on timeout. */
static bool scond_wait_win32(scond_t *cond, slock_t *lock, int64_t timeout_us)
{
struct scond_waiter w;
LARGE_INTEGER timeout;
uintptr_t old;
HANDLE timer = NULL;
bool woken = true;
w.event = NULL;
w.tid = GetCurrentThreadId();
retro_atomic_int_init(&w.flags, 0);
/* A bounded wait sleeps on the thread's event and its high
* resolution timer, whatever the tier, so it ends when it should
* rather than on the system timer's tick. Decided before the block
* is listed: the waker reads w.event to know how to wake it. */
if ( timeout_us > 0 && scond_g.hires
&& (timer = scond_thread_timer()))
{
if (!(w.event = scond_thread_event()))
timer = NULL;
}
if (!w.event && scond_g.sleep == SCOND_SLEEP_EVENT)
{
w.event = (HANDLE)TlsGetValue(scond_g.tls_event);
if (!w.event)
{
/* one auto-reset event per thread, kept for the thread's life */
w.event = CreateEvent(NULL, FALSE, FALSE, NULL);
if (!w.event)
return true; /* nothing to wait on: a spurious wake-up */
TlsSetValue(scond_g.tls_event, w.event);
}
}
/* push, keeping the lock bit as it is */
do
{
old = scond_head(cond);
w.next = (struct scond_waiter*)(old & SCOND_HEAD_MASK);
} while (!retro_atomic_cas_ptr(&cond->head, (void*)old,
(void*)((uintptr_t)&w | (old & SCOND_HEAD_LOCK))));
LeaveCriticalSection(&lock->lock);
/* spin on the flag word before committing to the kernel */
switch (scond_g.spin)
{
#if defined(SCOND_HAVE_X86)
#if defined(SCOND_HAVE_MWAITX)
case SCOND_SPIN_MWAITX:
{
unsigned __int64 deadline = scond_tsc() + scond_g.spin_cycles;
for (;;)
{
unsigned __int64 now;
/* arm first, then check: a write between the check and
* the wait is then seen by the wait itself */
scond_monitorx(&w.flags);
if (scond_flags_peek(&w) & SCOND_W_WOKEN)
goto done;
now = scond_tsc();
if (now >= deadline)
break;
scond_mwaitx((unsigned)(deadline - now));
}
}
break;
#endif
#if defined(SCOND_HAVE_UMWAIT)
case SCOND_SPIN_UMWAIT:
{
unsigned __int64 deadline = scond_tsc() + scond_g.spin_cycles;
for (;;)
{
scond_umonitor(&w.flags);
if (scond_flags_peek(&w) & SCOND_W_WOKEN)
goto done;
if (scond_tsc() >= deadline)
break;
scond_umwait(deadline);
}
}
break;
#endif
#endif
case SCOND_SPIN_PAUSE:
{
unsigned i;
for (i = 0; i < scond_g.spin_iters; i++)
{
if (scond_flags_peek(&w) & SCOND_W_WOKEN)
goto done;
#if defined(SCOND_HAVE_X86)
scond_pause();
#else
YieldProcessor();
#endif
}
}
break;
default:
break;
}
/* commit: after this a waker that takes the block must wake us */
if (retro_atomic_fetch_or_int(&w.flags, SCOND_W_ASLEEP) & SCOND_W_WOKEN)
goto done;
/* Without the timer the bound is in whole milliseconds, as it
* always was: under one is one, and over it rounds down */
if (timeout_us >= 0 && !timer)
timeout.QuadPart = -(LONGLONG)(timeout_us < 1000
? 1 : timeout_us / 1000) * 10000;
if (timer ? !scond_sleep_hires(&w, timer, timeout_us)
: !scond_sleep(&w, timeout_us >= 0 ? &timeout : NULL))
{
/* timed out: unless a waker has already taken the block, in which
* case its wake is on the way and has to be consumed - on the
* event, where the block slept on one */
scond_lock(cond);
woken = !scond_unlink(cond, &w);
scond_unlock(cond);
if (woken)
{
if (timer)
WaitForSingleObject(w.event, INFINITE);
else
scond_sleep(&w, NULL);
}
}
done:
EnterCriticalSection(&lock->lock);
return woken;
}
#endif
void scond_wait(scond_t *cond, slock_t *lock)
{
#if defined(USE_WIN32_THREADS)
scond_wait_win32(cond, lock, -1);
#elif defined(USE_GX_THREADS)
LWP_CondWait(cond->cond, lock->lock);
#elif defined(USE_CTR_THREADS)
CondVar_Wait(&cond->cond, &lock->lock);
#elif defined(USE_PSP_THREADS)
sceKernelWaitSema(cond->gate, 1, NULL);
cond->waiters++;
sceKernelSignalSema(cond->gate, 1);
slock_unlock(lock);
sceKernelWaitSema(cond->sema, 1, NULL);
slock_lock(lock);
#elif defined(USE_VITA_THREADS)
scond_bind_vita(cond, lock);
sceKernelWaitLwCond(&cond->work, NULL);
#elif defined(USE_PS3_THREADS)
scond_bind_ps3(cond, lock);
rthreads_ps3_lwcond_wait(&cond->work, RTHREADS_PS3_NO_TIMEOUT);
#elif defined(USE_WIIU_THREADS)
struct wiiu_cond_waiter w;
w.next = NULL;
OSInitEvent(&w.ev, FALSE, OS_EVENT_MODE_AUTO);
OSFastMutex_Lock(&cond->gate);
if (cond->tail)
cond->tail->next = &w;
else
cond->head = &w;
cond->tail = &w;
OSFastMutex_Unlock(&cond->gate);
slock_unlock(lock);
OSWaitEvent(&w.ev);
slock_lock(lock);
#elif defined(USE_SWITCH_THREADS)
condvarWait(&cond->cond, &lock->lock);
#elif defined(USE_PS2_THREADS)
struct ps2_cond_waiter w;
ps2_cond_enqueue(cond, &w);
slock_unlock(lock);
SleepThread();
slock_lock(lock);
#elif defined(RTHREADS_FUTEX_SCOND)
/* Registering and sampling seq happen under the caller's lock, so
* a signal issued once we let go either bumps seq before the futex
* looks at it (immediate return) or wakes the sleep. */
int seq = retro_atomic_load_acquire_int(&cond->seq);
retro_atomic_fetch_add_int(&cond->waiters, 1);
slock_unlock(lock);
syscall(__NR_futex, &cond->seq, RTHREADS_FUTEX_WAIT_BITSET_PRIVATE,
seq, NULL, NULL, RTHREADS_FUTEX_MATCH_ANY);
retro_atomic_fetch_add_int(&cond->waiters, -1);
slock_lock(lock);
#else
pthread_cond_wait(&cond->cond, &lock->lock);
#endif
}
int scond_broadcast(scond_t *cond)
{
#if defined(USE_WIN32_THREADS)
struct scond_waiter *w;
uintptr_t old;
if (!(scond_head(cond) & SCOND_HEAD_MASK))
return 0;
scond_lock(cond);
/* take the whole list, dropping the lock in the same swap */
do
{
old = scond_head(cond);
} while (!retro_atomic_cas_ptr(&cond->head, (void*)old, NULL));
w = (struct scond_waiter*)(old & SCOND_HEAD_MASK);
while (w)
{
struct scond_waiter *next = w->next;
scond_wake_one(w);
w = next;
}
return 0;
#elif defined(USE_GX_THREADS)
return LWP_CondBroadcast(cond->cond);
#elif defined(USE_CTR_THREADS)
CondVar_Broadcast(&cond->cond);
return 0;
#elif defined(USE_PSP_THREADS)
int n;
sceKernelWaitSema(cond->gate, 1, NULL);
n = cond->waiters;
cond->waiters = 0;
if (n > 0)
sceKernelSignalSema(cond->sema, n);
sceKernelSignalSema(cond->gate, 1);
return 0;
#elif defined(USE_VITA_THREADS)
if (retro_atomic_load_acquire_int(&cond->bound))
sceKernelSignalLwCondAll(&cond->work);
return 0;
#elif defined(USE_PS3_THREADS)
if (retro_atomic_load_acquire_int(&cond->bound))
rthreads_ps3_lwcond_signal_all(&cond->work);
return 0;
#elif defined(USE_WIIU_THREADS)
struct wiiu_cond_waiter *w, *next;
OSFastMutex_Lock(&cond->gate);
w = cond->head;
cond->head = NULL;
cond->tail = NULL;
OSFastMutex_Unlock(&cond->gate);
while (w)
{
/* The node vanishes with its waiter once signaled, so step off
* it first. */
next = w->next;
OSSignalEvent(&w->ev);
w = next;
}
return 0;
#elif defined(USE_SWITCH_THREADS)
condvarWakeAll(&cond->cond);
return 0;
#elif defined(USE_PS2_THREADS)
struct ps2_cond_waiter *w;
DIntr();
w = cond->head;
cond->head = NULL;
cond->tail = NULL;
EIntr();
while (w)
{
/* The node vanishes with its waiter once woken; take the id
* and the link first. */
struct ps2_cond_waiter *next = w->next;
s32 tid = w->tid;
w = next;
WakeupThread(tid);
}
return 0;
#elif defined(RTHREADS_FUTEX_SCOND)
if (retro_atomic_load_acquire_int(&cond->waiters) > 0)
{
retro_atomic_fetch_add_int(&cond->seq, 1);
syscall(__NR_futex, &cond->seq, RTHREADS_FUTEX_WAKE_PRIVATE,
0x7fffffff, NULL, NULL, 0);
}
return 0;
#else
return pthread_cond_broadcast(&cond->cond);
#endif
}
void scond_signal(scond_t *cond)
{
#if defined(USE_WIN32_THREADS)
struct scond_waiter *w;
if (!(scond_head(cond) & SCOND_HEAD_MASK))
return;
scond_lock(cond);
/* the oldest waiter is at the end: pushes go on the front */
for (;;)
{
uintptr_t old = scond_head(cond);
w = (struct scond_waiter*)(old & SCOND_HEAD_MASK);
if (!w)
break;
if (!w->next)
{
if (retro_atomic_cas_ptr(&cond->head, (void*)old,
(void*)SCOND_HEAD_LOCK))
break;
continue; /* a push landed in front: look again */
}
{
struct scond_waiter *prev = w;
while (prev->next->next)
prev = prev->next;
w = prev->next;
prev->next = NULL;
}
break;
}
scond_unlock(cond);
if (w)
scond_wake_one(w);
#elif defined(USE_GX_THREADS)
LWP_CondSignal(cond->cond);
#elif defined(USE_CTR_THREADS)
CondVar_Signal(&cond->cond);
#elif defined(USE_PSP_THREADS)
sceKernelWaitSema(cond->gate, 1, NULL);
if (cond->waiters > 0)
{
cond->waiters--;
sceKernelSignalSema(cond->sema, 1);
}
sceKernelSignalSema(cond->gate, 1);
#elif defined(USE_VITA_THREADS)
if (retro_atomic_load_acquire_int(&cond->bound))
sceKernelSignalLwCond(&cond->work);
#elif defined(USE_PS3_THREADS)
if (retro_atomic_load_acquire_int(&cond->bound))
rthreads_ps3_lwcond_signal(&cond->work);
#elif defined(USE_WIIU_THREADS)
struct wiiu_cond_waiter *w;
OSFastMutex_Lock(&cond->gate);
w = cond->head;
if (w)
{
cond->head = w->next;
if (!cond->head)
cond->tail = NULL;
}
OSFastMutex_Unlock(&cond->gate);
if (w)
OSSignalEvent(&w->ev);
#elif defined(USE_SWITCH_THREADS)
condvarWakeOne(&cond->cond);
#elif defined(USE_PS2_THREADS)
struct ps2_cond_waiter *w;
s32 tid = -1;
DIntr();
w = cond->head;
if (w)
{
tid = w->tid;
cond->head = w->next;
if (!cond->head)
cond->tail = NULL;
}
EIntr();
if (tid >= 0)
WakeupThread(tid);
#elif defined(RTHREADS_FUTEX_SCOND)
if (retro_atomic_load_acquire_int(&cond->waiters) > 0)
{
retro_atomic_fetch_add_int(&cond->seq, 1);
syscall(__NR_futex, &cond->seq, RTHREADS_FUTEX_WAKE_PRIVATE,
1, NULL, NULL, 0);
}
#else
pthread_cond_signal(&cond->cond);
#endif
}
bool scond_wait_timeout(scond_t *cond, slock_t *lock, int64_t timeout_us)
{
#if defined(USE_WIN32_THREADS)
/* How to convert a microsecond (us) timeout to millisecond (ms)?
*
* Someone asking for a 0 timeout clearly wants immediate timeout.
* Someone asking for a 1 timeout clearly wants an actual timeout
* of the minimum length */
/* The implementation of a 0 timeout here with pthreads is sketchy.
* It isn't clear what happens if pthread_cond_timedwait is called with NOW.
* Moreover, it is possible that this thread gets preempted after the
* clock_gettime but before the pthread_cond_timedwait.
* In order to help smoke out problems caused by this strange usage,
* let's treat a 0 timeout as always timing out.
*/
if (timeout_us == 0)
return false;
/* A deadline already past waits the shortest bound it always did,
* and is never taken for the unbounded wait */
if (timeout_us < 0)
timeout_us = 1000;
/* In microseconds: the wait rounds to whole milliseconds itself
* where it has no high resolution timer, as this did */
return scond_wait_win32(cond, lock, timeout_us);
#elif defined(USE_GX_THREADS)
/* libogc takes the timeout as a relative timespec; a zero timeout
* is treated as always timing out, as on Win32. */
struct timespec rel;
if (timeout_us <= 0)
return false;
rel.tv_sec = (time_t)(timeout_us / INT64_C(1000000));
rel.tv_nsec = (long)(timeout_us % INT64_C(1000000)) * 1000L;
return LWP_CondTimedWait(cond->cond, lock->lock, &rel) == 0;
#elif defined(USE_CTR_THREADS)
if (timeout_us <= 0)
return false;
return CondVar_WaitTimeout(&cond->cond, &lock->lock,
timeout_us * INT64_C(1000)) == 0;
#elif defined(USE_PSP_THREADS)
bool woken;
SceUInt to;
if (timeout_us <= 0)
return false;
to = (timeout_us > INT64_C(0xFFFFFFFF))
? 0xFFFFFFFFu : (SceUInt)timeout_us;
sceKernelWaitSema(cond->gate, 1, NULL);
cond->waiters++;
sceKernelSignalSema(cond->gate, 1);
slock_unlock(lock);
woken = sceKernelWaitSema(cond->sema, 1, &to) == 0;
if (!woken)
{
/* Timed out: withdraw the registration, unless a wake credit
* arrived in the meantime, in which case consume it and report
* the wake. */
sceKernelWaitSema(cond->gate, 1, NULL);
if (sceKernelPollSema(cond->sema, 1) == 0)
woken = true;
else
cond->waiters--;
sceKernelSignalSema(cond->gate, 1);
}
slock_lock(lock);
return woken;
#elif defined(USE_PS3_THREADS)
if (timeout_us <= 0)
return false;
scond_bind_ps3(cond, lock);
return rthreads_ps3_lwcond_wait(&cond->work,
(uint64_t)timeout_us) == 0;
#elif defined(USE_VITA_THREADS)
unsigned int to;
if (timeout_us <= 0)
return false;
scond_bind_vita(cond, lock);
to = (timeout_us > INT64_C(0xFFFFFFFF))
? 0xFFFFFFFFu : (unsigned int)timeout_us;
return sceKernelWaitLwCond(&cond->work, &to) == 0;
#elif defined(USE_WIIU_THREADS)
bool woken;
struct wiiu_cond_waiter w;
if (timeout_us <= 0)
return false;
w.next = NULL;
OSInitEvent(&w.ev, FALSE, OS_EVENT_MODE_AUTO);
OSFastMutex_Lock(&cond->gate);
if (cond->tail)
cond->tail->next = &w;
else
cond->head = &w;
cond->tail = &w;
OSFastMutex_Unlock(&cond->gate);
slock_unlock(lock);
woken = OSWaitEventWithTimeout(&w.ev,
(OSTime)OSMicroseconds(timeout_us));
if (!woken)
{
/* Timed out: withdraw the registration, unless a signaler
* already dequeued this waiter - then its wake is in flight, so
* consume it before the node on this stack goes away, and
* report the wake. */
struct wiiu_cond_waiter *cur, *prev = NULL;
OSFastMutex_Lock(&cond->gate);
cur = cond->head;
while (cur && cur != &w)
{
prev = cur;
cur = cur->next;
}
if (cur)
{
if (prev)
prev->next = w.next;
else
cond->head = w.next;
if (cond->tail == &w)
cond->tail = prev;
}
OSFastMutex_Unlock(&cond->gate);
if (!cur)
{
OSWaitEvent(&w.ev);
woken = true;
}
}
slock_lock(lock);
return woken;
#elif defined(USE_SWITCH_THREADS)
if (timeout_us <= 0)
return false;
return condvarWaitTimeout(&cond->cond, &lock->lock,
(u64)timeout_us * 1000ull) == 0;
#elif defined(USE_PS2_THREADS)
struct ps2_cond_waiter w;
bool woken;
s32 alarm;
if (timeout_us <= 0)
return false;
ps2_cond_enqueue(cond, &w);
slock_unlock(lock);
alarm = SetTimerAlarm(GetTimerSystemTime()
+ TimerUSec2BusClock((u32)(timeout_us / INT64_C(1000000)),
(u32)(timeout_us % INT64_C(1000000))),
ps2_cond_alarm, &w);
if (alarm < 0)
{
/* No alarm slot free: report a timeout right away, unless a
* signaler has already claimed this waiter, whose wake must
* then be consumed. */
if (ps2_cond_withdraw(cond, &w))
woken = false;
else
{
SleepThread();
woken = true;
}
slock_lock(lock);
return woken;
}
SleepThread();
/* Which of the alarm and a signaler woke us. Releasing under
* DIntr means the handler cannot slip in between the check and
* the release. */
DIntr();
if (!w.fired)
iReleaseTimerAlarm(alarm);
EIntr();
if (!w.fired)
woken = true; /* a signaler dequeued this node */
else if (ps2_cond_withdraw(cond, &w))
woken = false; /* nobody signaled: timed out */
else
{
/* Both the alarm and a signaler fired, so one wakeup is still
* banked against this thread; drain it before it can end a
* later sleep early. */
CancelWakeupThread(w.tid);
woken = true;
}
slock_lock(lock);
return woken;
#elif defined(USE_GEKKO_THREADS)
if (timeout_us <= 0)
return false;
return gk_cond_wait(&cond->cond, &lock->lock,
GK_US_TO_TICKS(timeout_us)) != GK_ETIMEDOUT;
#elif defined(RTHREADS_FUTEX_SCOND)
/* FUTEX_WAIT_BITSET takes an absolute CLOCK_MONOTONIC deadline, so
* a spurious wake never shortens or stretches the wait. */
struct timespec dl;
int seq, rc;
if (timeout_us <= 0)
return false;
clock_gettime(CLOCK_MONOTONIC, &dl);
dl.tv_sec += (time_t)(timeout_us / INT64_C(1000000));
dl.tv_nsec += (long)(timeout_us % INT64_C(1000000)) * 1000L;
if (dl.tv_nsec >= 1000000000L)
{
dl.tv_sec += 1;
dl.tv_nsec -= 1000000000L;
}
seq = retro_atomic_load_acquire_int(&cond->seq);
retro_atomic_fetch_add_int(&cond->waiters, 1);
slock_unlock(lock);
rc = (int)syscall(__NR_futex, &cond->seq,
RTHREADS_FUTEX_WAIT_BITSET_PRIVATE, seq, &dl, NULL,
RTHREADS_FUTEX_MATCH_ANY);
retro_atomic_fetch_add_int(&cond->waiters, -1);
slock_lock(lock);
return !(rc < 0 && errno == ETIMEDOUT);
#else
int64_t seconds, remainder;
struct timespec now;
#if defined(__MACH__) && defined(__APPLE__) && !defined(RTHREADS_HAVE_CLOCK_GETTIME)
mach_timespec_t mts;
#endif
#if defined(__MACH__) && defined(__APPLE__)
/* CALENDAR_CLOCK is the Mach equivalent of CLOCK_REALTIME, which is what
* pthread_cond_timedwait() below expects. */
#ifdef RTHREADS_HAVE_CLOCK_GETTIME
clock_gettime(CLOCK_REALTIME, &now);
#else
pthread_once(&rthreads_calendar_clock_once, rthreads_calendar_clock_init);
clock_get_time(rthreads_calendar_clock, &mts);
now.tv_sec = mts.tv_sec;
now.tv_nsec = mts.tv_nsec;
#endif
#elif defined(RETRO_WIN32_USE_PTHREADS)
{
/* _ftime64_s fills a __timeb64, not a timespec: seconds and
* milliseconds, which pthread_cond_timedwait wants as seconds
* and nanoseconds since the epoch. */
struct __timeb64 tb;
_ftime64_s(&tb);
now.tv_sec = (time_t)tb.time;
now.tv_nsec = (long)tb.millitm * 1000000L;
}
#else
clock_gettime(CLOCK_REALTIME, &now);
#endif
seconds = timeout_us / INT64_C(1000000);
remainder = timeout_us % INT64_C(1000000);
now.tv_sec += seconds;
now.tv_nsec += remainder * INT64_C(1000);
if (now.tv_nsec >= 1000000000)
{
now.tv_nsec -= 1000000000;
now.tv_sec += 1;
}
return (pthread_cond_timedwait(&cond->cond, &lock->lock, &now) == 0);
#endif
}
#ifdef HAVE_THREAD_STORAGE
bool sthread_tls_create(sthread_tls_t *tls)
{
#ifdef USE_WIN32_THREADS
return (*tls = TlsAlloc()) != TLS_OUT_OF_INDEXES;
#else
return pthread_key_create((pthread_key_t*)tls, NULL) == 0;
#endif
}
bool sthread_tls_create_with_dtor(sthread_tls_t *tls,
void (*destructor)(void *value))
{
#ifdef USE_WIN32_THREADS
/* TlsAlloc() provides no destructor callback; created without one. */
(void)destructor;
return (*tls = TlsAlloc()) != TLS_OUT_OF_INDEXES;
#else
return pthread_key_create((pthread_key_t*)tls, destructor) == 0;
#endif
}
bool sthread_tls_delete(sthread_tls_t *tls)
{
#ifdef USE_WIN32_THREADS
return TlsFree(*tls) != 0;
#else
return pthread_key_delete(*tls) == 0;
#endif
}
void *sthread_tls_get(sthread_tls_t *tls)
{
#ifdef USE_WIN32_THREADS
return TlsGetValue(*tls);
#else
return pthread_getspecific(*tls);
#endif
}
bool sthread_tls_set(sthread_tls_t *tls, const void *data)
{
#ifdef USE_WIN32_THREADS
return TlsSetValue(*tls, (void*)data) != 0;
#else
return pthread_setspecific(*tls, data) == 0;
#endif
}
#endif
uintptr_t sthread_get_thread_id(sthread_t *thread)
{
if (thread)
#ifdef USE_SWITCH_THREADS
return (uintptr_t)&thread->t;
#else
return (uintptr_t)thread->id;
#endif
return 0;
}
uintptr_t sthread_get_current_thread_id(void)
{
#if defined(USE_WIN32_THREADS)
return (uintptr_t)GetCurrentThreadId();
#elif defined(USE_GX_THREADS)
return (uintptr_t)LWP_GetSelf();
#elif defined(USE_CTR_THREADS)
return (uintptr_t)threadGetCurrent();
#elif defined(RTHREADS_SCE_START)
return (uintptr_t)sceKernelGetThreadId();
#elif defined(USE_WIIU_THREADS)
return (uintptr_t)OSGetCurrentThread();
#elif defined(USE_SWITCH_THREADS)
return (uintptr_t)threadGetSelf();
#elif defined(USE_PS2_THREADS)
return (uintptr_t)GetThreadId();
#elif defined(USE_PS3_THREADS)
{
sys_ppu_thread_t self = 0;
rthreads_ps3_thread_get_id(&self);
return (uintptr_t)self;
}
#else
return (uintptr_t)pthread_self();
#endif
}
bool sthread_is_main_thread(void)
{
#if defined(__APPLE__)
/* BSD/Darwin extension reporting whether the caller is the initial
* thread. pthread.h is already included on this backend. */
return pthread_main_np() != 0;
#else
/* No native predicate on this backend; current callers are Apple-only.
* See the header note for the portable captured-id alternative. */
return false;
#endif
}
/* pthread_cancel / pthread_setcancelstate are POSIX but not universally
* available: notably absent on Android/Bionic, and meaningless on the
* non-pthread backends. Enable only where the backend provides them. */
#if !defined(USE_WIN32_THREADS) && !defined(USE_GX_THREADS) \
&& !defined(USE_CTR_THREADS) && !defined(USE_PSP_THREADS) \
&& !defined(USE_VITA_THREADS) && !defined(USE_WIIU_THREADS) \
&& !defined(USE_SWITCH_THREADS) && !defined(USE_PS2_THREADS) \
&& !defined(USE_PS3_THREADS) && !defined(USE_GEKKO_THREADS) \
&& !defined(__ANDROID__)
#define RTHREADS_HAVE_CANCEL 1
#endif
void sthread_set_cancel_enable(bool enable)
{
#ifdef RTHREADS_HAVE_CANCEL
pthread_setcancelstate(
enable ? PTHREAD_CANCEL_ENABLE : PTHREAD_CANCEL_DISABLE, NULL);
#else
(void)enable;
#endif
}
bool sthread_cancel(sthread_t *thread)
{
#ifdef RTHREADS_HAVE_CANCEL
if (thread)
return pthread_cancel(thread->id) == 0;
return false;
#else
(void)thread;
return false;
#endif
}
void *sthread_priority_override_begin(void)
{
#ifdef RTHREADS_HAVE_QOS_OVERRIDE
return (void*)pthread_override_qos_class_start_np(
pthread_self(), QOS_CLASS_USER_INTERACTIVE, 0);
#else
return NULL;
#endif
}
void sthread_priority_override_end(void *ovr)
{
#ifdef RTHREADS_HAVE_QOS_OVERRIDE
if (ovr)
pthread_override_qos_class_end_np((pthread_override_t)ovr);
#else
(void)ovr;
#endif
}