mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-07 08:36:02 -10:00
Wii U: newlib locks on Cafe OS mutexes
devkitPPC's newlib serialises stdio, the environment, the time zone and the atexit lists only through the weak __syscall_lock_* hooks, which nothing on Wii U defined, so with threads on, libc state shared between the frontend's threads went unlocked. wiiu/system/newlib_locks.c provides them on OSMutex: newlib's lock word starts at 0, a mutex is created on first use and published into it by compare-and-swap, and OSMutex being recursive serves the recursive locks as well. samples/frontend/wiiu_newlib_locks runs the hooks against stand-in mutexes: threads racing first use leave one mutex published and the counter it guards exact, plus the recursive, try and close paths.
This commit is contained in:
1 parent
412d2c4224
commit
06a85bc900
5 files changed
+368
No files matched your search
@@ -43,6 +43,7 @@ OBJ += wiiu/system/memory.o
|
||||
OBJ += wiiu/system/atomic.o
|
||||
OBJ += wiiu/system/exception_handler.o
|
||||
OBJ += wiiu/system/missing_libc_functions.o
|
||||
OBJ += wiiu/system/newlib_locks.o
|
||||
OBJ += wiiu/fs/sd_fat_devoptab.o
|
||||
OBJ += wiiu/fs/fs_utils.o
|
||||
OBJ += wiiu/hbl.o
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
# wiiu/system/newlib_locks.c, the newlib lock hooks on Cafe OS mutexes,
|
||||
# against stand-in OSMutex functions. The Wii U headers are the tree's
|
||||
# own (wiiu/include). Builds for Linux and, with
|
||||
# CC=x86_64-w64-mingw32-gcc EXE=.exe RUNNER=wine, for Windows.
|
||||
TARGET := wiiu_newlib_locks_test$(EXE)
|
||||
CORE_DIR := ../../..
|
||||
|
||||
CFLAGS += -std=gnu89 -Wall -g1 -O1 -I$(CORE_DIR)/wiiu/include
|
||||
LDFLAGS += -lpthread
|
||||
|
||||
# The lock word is an int holding a mutex address, as on the Wii U, so
|
||||
# the test's static pool must sit below 2^31.
|
||||
ifeq ($(EXE),.exe)
|
||||
LDFLAGS += -static -Wl,--image-base=0x10000000 -Wl,--disable-dynamicbase \
|
||||
-Wl,--disable-high-entropy-va
|
||||
else
|
||||
CFLAGS += -fno-pie
|
||||
LDFLAGS += -no-pie
|
||||
endif
|
||||
|
||||
ifneq ($(SANITIZER),)
|
||||
CFLAGS := -fsanitize=$(SANITIZER) -fno-omit-frame-pointer $(CFLAGS)
|
||||
LDFLAGS := -fsanitize=$(SANITIZER) $(LDFLAGS)
|
||||
endif
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): wiiu_newlib_locks_test.c $(CORE_DIR)/wiiu/system/newlib_locks.c
|
||||
$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)
|
||||
|
||||
check: all
|
||||
$(RUNNER) ./$(TARGET)
|
||||
|
||||
clean:
|
||||
rm -f wiiu_newlib_locks_test wiiu_newlib_locks_test.exe
|
||||
|
||||
.PHONY: all check clean
|
||||
@@ -0,0 +1,194 @@
|
||||
/* wiiu/system/newlib_locks.c against stand-in Cafe OS mutexes: newlib's
|
||||
* lock word starts at 0, several threads race to create its mutex,
|
||||
* exactly one mutex is published and kept, and it excludes. The
|
||||
* recursive, try and close paths are checked on one thread and
|
||||
* across two.
|
||||
*
|
||||
* newlib's lock word is an int holding the mutex address, as on the
|
||||
* Wii U's 32-bit address space, so the stand-in allocator hands out
|
||||
* slots from a static pool and the executable is linked at a low
|
||||
* fixed base (see the Makefile) to keep those addresses below 2^31. */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include <wiiu/os/mutex.h>
|
||||
|
||||
#define POOL_SLOTS 64
|
||||
#define THREADS 8
|
||||
#define ROUNDS 20000
|
||||
|
||||
struct slot
|
||||
{
|
||||
OSMutex os; /* first: the pointer the code under test sees */
|
||||
pthread_mutex_t pm;
|
||||
int live;
|
||||
};
|
||||
|
||||
static struct slot pool[POOL_SLOTS];
|
||||
static int pool_next;
|
||||
static int pool_live;
|
||||
static int failures;
|
||||
|
||||
#define CHECK(cond) do { if (!(cond)) { \
|
||||
fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \
|
||||
failures++; } } while (0)
|
||||
|
||||
static void *test_malloc(size_t size)
|
||||
{
|
||||
int i;
|
||||
if (size != sizeof(OSMutex))
|
||||
{
|
||||
fprintf(stderr, "FAIL unexpected allocation of %u bytes\n",
|
||||
(unsigned)size);
|
||||
exit(1);
|
||||
}
|
||||
i = __atomic_fetch_add(&pool_next, 1, __ATOMIC_ACQ_REL);
|
||||
if (i >= POOL_SLOTS)
|
||||
return NULL;
|
||||
if ((uintptr_t)(unsigned)(uintptr_t)&pool[i].os != (uintptr_t)&pool[i].os
|
||||
|| (uintptr_t)&pool[i].os > 0x7fffffffu)
|
||||
{
|
||||
fprintf(stderr, "FAIL pool above 2^31: link at a low base\n");
|
||||
exit(1);
|
||||
}
|
||||
return &pool[i].os;
|
||||
}
|
||||
|
||||
static void test_free(void *p)
|
||||
{
|
||||
struct slot *s = (struct slot*)p;
|
||||
if (!p)
|
||||
return;
|
||||
if (s->live)
|
||||
{
|
||||
pthread_mutex_destroy(&s->pm);
|
||||
s->live = 0;
|
||||
__atomic_fetch_sub(&pool_live, 1, __ATOMIC_ACQ_REL);
|
||||
}
|
||||
}
|
||||
|
||||
/* Stand-ins for coreinit. OSMutex is recursive. */
|
||||
void OSInitMutex(OSMutex *mutex)
|
||||
{
|
||||
struct slot *s = (struct slot*)mutex;
|
||||
pthread_mutexattr_t attr;
|
||||
pthread_mutexattr_init(&attr);
|
||||
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
|
||||
pthread_mutex_init(&s->pm, &attr);
|
||||
pthread_mutexattr_destroy(&attr);
|
||||
s->live = 1;
|
||||
__atomic_fetch_add(&pool_live, 1, __ATOMIC_ACQ_REL);
|
||||
}
|
||||
|
||||
void OSLockMutex(OSMutex *mutex)
|
||||
{
|
||||
pthread_mutex_lock(&((struct slot*)mutex)->pm);
|
||||
}
|
||||
|
||||
BOOL OSTryLockMutex(OSMutex *mutex)
|
||||
{
|
||||
return pthread_mutex_trylock(&((struct slot*)mutex)->pm) == 0;
|
||||
}
|
||||
|
||||
void OSUnlockMutex(OSMutex *mutex)
|
||||
{
|
||||
pthread_mutex_unlock(&((struct slot*)mutex)->pm);
|
||||
}
|
||||
|
||||
#define malloc test_malloc
|
||||
#define free test_free
|
||||
#include "../../../wiiu/system/newlib_locks.c"
|
||||
#undef malloc
|
||||
#undef free
|
||||
|
||||
static int word; /* a newlib static lock: starts at 0 */
|
||||
static unsigned counter; /* guarded by word */
|
||||
static pthread_barrier_t start;
|
||||
|
||||
static void *hammer(void *arg)
|
||||
{
|
||||
int i;
|
||||
(void)arg;
|
||||
pthread_barrier_wait(&start);
|
||||
for (i = 0; i < ROUNDS; i++)
|
||||
{
|
||||
unsigned v;
|
||||
__syscall_lock_acquire_recursive(&word);
|
||||
v = counter;
|
||||
counter = v + 1;
|
||||
__syscall_lock_release_recursive(&word);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int try_result;
|
||||
|
||||
static void *try_other(void *arg)
|
||||
{
|
||||
try_result = __syscall_lock_try_acquire_recursive((int*)arg);
|
||||
if (try_result == 0)
|
||||
__syscall_lock_release_recursive((int*)arg);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int try_from_other_thread(int *lock)
|
||||
{
|
||||
pthread_t t;
|
||||
pthread_create(&t, NULL, try_other, lock);
|
||||
pthread_join(t, NULL);
|
||||
return try_result;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
pthread_t t[THREADS];
|
||||
int i;
|
||||
int rec = 0;
|
||||
|
||||
/* Racing first use: one mutex published, the losers' freed. */
|
||||
pthread_barrier_init(&start, NULL, THREADS);
|
||||
for (i = 0; i < THREADS; i++)
|
||||
pthread_create(&t[i], NULL, hammer, NULL);
|
||||
for (i = 0; i < THREADS; i++)
|
||||
pthread_join(t[i], NULL);
|
||||
pthread_barrier_destroy(&start);
|
||||
CHECK(counter == (unsigned)THREADS * ROUNDS);
|
||||
CHECK(word != 0);
|
||||
CHECK(pool_live == 1);
|
||||
|
||||
/* Recursive on one thread, excluded from another. */
|
||||
__syscall_lock_init_recursive(&rec);
|
||||
CHECK(rec == 0);
|
||||
__syscall_lock_acquire_recursive(&rec);
|
||||
CHECK(__syscall_lock_try_acquire_recursive(&rec) == 0);
|
||||
CHECK(try_from_other_thread(&rec) != 0);
|
||||
__syscall_lock_release_recursive(&rec);
|
||||
CHECK(try_from_other_thread(&rec) != 0);
|
||||
__syscall_lock_release_recursive(&rec);
|
||||
CHECK(try_from_other_thread(&rec) == 0);
|
||||
|
||||
/* Close frees the mutex and returns the word to its initial state;
|
||||
* the next use creates a new one. */
|
||||
__syscall_lock_close_recursive(&rec);
|
||||
CHECK(rec == 0);
|
||||
CHECK(pool_live == 1);
|
||||
__syscall_lock_acquire(&rec);
|
||||
CHECK(rec != 0);
|
||||
CHECK(pool_live == 2);
|
||||
__syscall_lock_release(&rec);
|
||||
__syscall_lock_close(&rec);
|
||||
__syscall_lock_close(&word);
|
||||
CHECK(pool_live == 0);
|
||||
|
||||
if (failures)
|
||||
{
|
||||
fprintf(stderr, "%d failure(s)\n", failures);
|
||||
return 1;
|
||||
}
|
||||
printf("wiiu_newlib_locks: ok\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -17,6 +17,7 @@ IMPORT(OSSavesDone_ReadyToRelease);
|
||||
IMPORT(OSInitMutex);
|
||||
IMPORT(OSLockMutex);
|
||||
IMPORT(OSUnlockMutex);
|
||||
IMPORT(OSTryLockMutex);
|
||||
IMPORT(OSInitCond);
|
||||
IMPORT(OSWaitCond);
|
||||
IMPORT(OSSignalCond);
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
/* RetroArch - A frontend for libretro.
|
||||
*
|
||||
* RetroArch is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with RetroArch.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/* devkitPPC's newlib guards stdio, the environment, the time zone and
|
||||
* the atexit lists through weak __syscall_lock_* hooks and does no
|
||||
* locking when nothing defines them. These give it Cafe OS mutexes,
|
||||
* so libc state shared between the frontend's threads is serialised.
|
||||
*
|
||||
* Its lock word (_LOCK_T, and _LOCK_RECURSIVE_T alike) is a plain int
|
||||
* that newlib's static locks start at 0, so a mutex is created on
|
||||
* first use and its address published into the word by compare-and-
|
||||
* swap. OSMutex is recursive, which serves both kinds of lock. */
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <wiiu/os/mutex.h>
|
||||
|
||||
typedef int wiiu_newlib_lock_t;
|
||||
|
||||
static OSMutex *wiiu_newlib_mutex(wiiu_newlib_lock_t *lock)
|
||||
{
|
||||
OSMutex *mutex = (OSMutex*)(uintptr_t)
|
||||
__atomic_load_n(lock, __ATOMIC_ACQUIRE);
|
||||
int expected = 0;
|
||||
|
||||
if (mutex)
|
||||
return mutex;
|
||||
|
||||
if (!(mutex = (OSMutex*)malloc(sizeof(*mutex))))
|
||||
return NULL;
|
||||
OSInitMutex(mutex);
|
||||
|
||||
if (!__atomic_compare_exchange_n(lock, &expected,
|
||||
(int)(uintptr_t)mutex, 0,
|
||||
__ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE))
|
||||
{
|
||||
/* Another thread published its mutex first. */
|
||||
free(mutex);
|
||||
mutex = (OSMutex*)(uintptr_t)expected;
|
||||
}
|
||||
return mutex;
|
||||
}
|
||||
|
||||
static void wiiu_newlib_lock_free(wiiu_newlib_lock_t *lock)
|
||||
{
|
||||
OSMutex *mutex = (OSMutex*)(uintptr_t)
|
||||
__atomic_exchange_n(lock, 0, __ATOMIC_ACQ_REL);
|
||||
free(mutex);
|
||||
}
|
||||
|
||||
static void wiiu_newlib_lock_acquire(wiiu_newlib_lock_t *lock)
|
||||
{
|
||||
OSMutex *mutex = wiiu_newlib_mutex(lock);
|
||||
if (mutex)
|
||||
OSLockMutex(mutex);
|
||||
}
|
||||
|
||||
static int wiiu_newlib_lock_try_acquire(wiiu_newlib_lock_t *lock)
|
||||
{
|
||||
/* newlib's convention: 0 when the lock was taken. */
|
||||
OSMutex *mutex = wiiu_newlib_mutex(lock);
|
||||
if (!mutex)
|
||||
return 0;
|
||||
return OSTryLockMutex(mutex) ? 0 : 1;
|
||||
}
|
||||
|
||||
static void wiiu_newlib_lock_release(wiiu_newlib_lock_t *lock)
|
||||
{
|
||||
OSMutex *mutex = (OSMutex*)(uintptr_t)
|
||||
__atomic_load_n(lock, __ATOMIC_ACQUIRE);
|
||||
if (mutex)
|
||||
OSUnlockMutex(mutex);
|
||||
}
|
||||
|
||||
void __syscall_lock_init(wiiu_newlib_lock_t *lock)
|
||||
{
|
||||
*lock = 0;
|
||||
}
|
||||
|
||||
void __syscall_lock_acquire(wiiu_newlib_lock_t *lock)
|
||||
{
|
||||
wiiu_newlib_lock_acquire(lock);
|
||||
}
|
||||
|
||||
int __syscall_lock_try_acquire(wiiu_newlib_lock_t *lock)
|
||||
{
|
||||
return wiiu_newlib_lock_try_acquire(lock);
|
||||
}
|
||||
|
||||
void __syscall_lock_release(wiiu_newlib_lock_t *lock)
|
||||
{
|
||||
wiiu_newlib_lock_release(lock);
|
||||
}
|
||||
|
||||
void __syscall_lock_close(wiiu_newlib_lock_t *lock)
|
||||
{
|
||||
wiiu_newlib_lock_free(lock);
|
||||
}
|
||||
|
||||
void __syscall_lock_init_recursive(wiiu_newlib_lock_t *lock)
|
||||
{
|
||||
*lock = 0;
|
||||
}
|
||||
|
||||
void __syscall_lock_acquire_recursive(wiiu_newlib_lock_t *lock)
|
||||
{
|
||||
wiiu_newlib_lock_acquire(lock);
|
||||
}
|
||||
|
||||
int __syscall_lock_try_acquire_recursive(wiiu_newlib_lock_t *lock)
|
||||
{
|
||||
return wiiu_newlib_lock_try_acquire(lock);
|
||||
}
|
||||
|
||||
void __syscall_lock_release_recursive(wiiu_newlib_lock_t *lock)
|
||||
{
|
||||
wiiu_newlib_lock_release(lock);
|
||||
}
|
||||
|
||||
void __syscall_lock_close_recursive(wiiu_newlib_lock_t *lock)
|
||||
{
|
||||
wiiu_newlib_lock_free(lock);
|
||||
}
|
||||
Reference in new issue
Block a user