/* ** mkxp-polyfill.cpp ** ** This file is part of mkxp. ** ** Copyright (C) 2013 - 2021 Amaryllis Kulla ** ** mkxp 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 Foundation, either version 2 of the License, or ** (at your option) any later version. ** ** mkxp 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 mkxp. If not, see . */ #include "mkxp-polyfill.h" #include #include #if defined(MKXPZ_NO_SPRINTF) || defined(MKXPZ_NO_SNPRINTF) || defined(MKXPZ_NO_VSPRINTF) || defined(MKXPZ_NO_VSNPRINTF) # include #endif #ifdef MKXPZ_HAVE_ALIGNED_MALLOC # include #endif #ifndef MKXPZ_NO_STD_THIS_THREAD_SLEEP_FOR # include #elif !defined(MKXPZ_NO_USLEEP) # include #elif !defined(MKXPZ_NO_NANOSLEEP) # include #endif #ifndef MKXPZ_NO_STD_MUTEX # include #endif #ifdef MKXPZ_NO_SPRINTF extern "C" int sprintf(char *buffer, const char *format, ...) { va_list vlist; va_start(vlist, format); int result = stbsp_vsprintf(buffer, buf_size, format, vlist); va_end(vlist); return result; } #endif #ifdef MKXPZ_NO_SNPRINTF extern "C" int snprintf(char *buffer, size_t buf_size, const char *format, ...) { va_list vlist; va_start(vlist, format); int result = stbsp_vsnprintf(buffer, buf_size, format, vlist); va_end(vlist); return result; } #endif #ifdef MKXPZ_NO_VSPRINTF extern "C" int vsprintf(char *buffer, const char *format, va_list vlist) { return stbsp_vsprintf(buffer, buf_size, format, vlist); } #endif #ifdef MKXPZ_NO_VSNPRINTF extern "C" int vsnprintf(char *buffer, size_t buf_size, const char *format, va_list vlist) { return stbsp_vsnprintf(buffer, buf_size, format, vlist); } #endif extern "C" void *mkxp_aligned_malloc(size_t alignment, size_t size) { #if defined(MKXPZ_HAVE_POSIX_MEMALIGN) || defined(MKXPZ_BUILD_XCODE) void *mem; return posix_memalign(&mem, alignment, size) ? NULL : mem; #elif defined(MKXPZ_HAVE_ALIGNED_MALLOC) return _aligned_malloc(size, alignment); #elif defined(MKXPZ_HAVE_ALIGNED_ALLOC) return aligned_alloc(alignment, size); #else return std::malloc(size); #endif } extern "C" void mkxp_aligned_free(void *ptr) { #if defined(MKXPZ_HAVE_ALIGNED_MALLOC) _aligned_free(ptr); #else std::free(ptr); #endif } #if defined(MKXPZ_DEVKITARM_NO_PTHREAD_H_MUTEX) || defined(MKXPZ_DEVKITARM_NO_PTHREAD_H_THREAD) extern "C" { void LightLock_Init(_LOCK_T *); void LightLock_Lock(_LOCK_T *); void LightLock_Unlock(_LOCK_T *); int LightLock_TryLock(_LOCK_T *); void RecursiveLock_Init(_LOCK_RECURSIVE_T *); void RecursiveLock_Lock(_LOCK_RECURSIVE_T *); void RecursiveLock_Unlock(_LOCK_RECURSIVE_T *); int RecursiveLock_TryLock(_LOCK_RECURSIVE_T *); void CondVar_Init(mkxp_cond_t *); void CondVar_Wait(mkxp_cond_t *, _LOCK_T *); void CondVar_WakeUp(mkxp_cond_t *, int32_t); uint32_t APT_CheckNew3DS(bool *out); uint32_t svcGetThreadPriority(int32_t *out, uint32_t handle); void *threadCreate(void (*entrypoint)(void *), void *arg, size_t stack_size, int prio, int core_id, bool detached); void *threadJoin(void *thread, uint64_t timeout_ns); void threadFree(void *thread); void *threadGetCurrent(void); }; static bool mutex_inited = false; static _LOCK_T safe_double_thread_launch; static void *(*start_routine_jump)(void *); static void ctr_thread_launcher(void *data) { // Copied from https://github.com/libretro/libretro-common/blob/master/rthreads/ctr_pthread.h (licensed MIT) void *(*start_routine_jump_safe)(void*) = start_routine_jump; LightLock_Unlock(&safe_double_thread_launch); start_routine_jump_safe(data); } #endif #if defined(MKXPZ_NO_SEMAPHORE_H) && !defined(MKXPZ_NO_PTHREAD_H_MUTEX) struct mkxp_sem_private { unsigned int value; mkxp_mutex_t mutex; mkxp_cond_t cond; }; #endif extern "C" int mkxp_mutex_init(mkxp_mutex_t *mutex, bool recursive) { #ifndef MKXPZ_NO_STD_MUTEX mutex->recursive = recursive; if (recursive) { mutex->inner = new std::recursive_mutex; } else { mutex->inner = new std::mutex; } return 0; #elif defined(MKXPZ_DEVKITARM_NO_PTHREAD_H_MUTEX) mutex->recursive = recursive; if (recursive) { RecursiveLock_Init(&mutex->inner.recursive); } else { LightLock_Init(&mutex->inner.light); } return 0; #elif !defined(MKXPZ_NO_PTHREAD_H_MUTEX) if (recursive) { pthread_mutexattr_t attr; if (pthread_mutexattr_init(&attr)) { return -1; } if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE)) { return -1; } return pthread_mutex_init(mutex, &attr); } else { return pthread_mutex_init(mutex, NULL); } #else *mutex = 0; return 0; #endif } extern "C" int mkxp_mutex_destroy(mkxp_mutex_t *mutex) { #ifndef MKXPZ_NO_STD_MUTEX if (mutex->recursive) { delete (std::recursive_mutex *)mutex->inner; } else { delete (std::mutex *)mutex->inner; } return 0; #elif defined(MKXPZ_DEVKITARM_NO_PTHREAD_H_MUTEX) return 0; #elif !defined(MKXPZ_NO_PTHREAD_H_MUTEX) return pthread_mutex_destroy(mutex); #else assert(!*mutex); return 0; #endif } extern "C" int mkxp_mutex_lock(mkxp_mutex_t *mutex) { #ifndef MKXPZ_NO_STD_MUTEX if (mutex->recursive) { ((std::recursive_mutex *)mutex->inner)->lock(); } else { ((std::mutex *)mutex->inner)->lock(); } return 0; #elif defined(MKXPZ_DEVKITARM_NO_PTHREAD_H_MUTEX) if (mutex->recursive) { RecursiveLock_Lock(&mutex->inner.recursive); } else { LightLock_Lock(&mutex->inner.light); } return 0; #elif !defined(MKXPZ_NO_PTHREAD_H_MUTEX) return pthread_mutex_lock(mutex); #else ++*mutex; return 0; #endif } extern "C" int mkxp_mutex_unlock(mkxp_mutex_t *mutex) { #ifndef MKXPZ_NO_STD_MUTEX if (mutex->recursive) { ((std::recursive_mutex *)mutex->inner)->unlock(); } else { ((std::mutex *)mutex->inner)->unlock(); } return 0; #elif defined(MKXPZ_DEVKITARM_NO_PTHREAD_H_MUTEX) if (mutex->recursive) { RecursiveLock_Unlock(&mutex->inner.recursive); } else { LightLock_Unlock(&mutex->inner.light); } return 0; #elif !defined(MKXPZ_NO_PTHREAD_H_MUTEX) return pthread_mutex_unlock(mutex); #else assert(*mutex); --*mutex; return 0; #endif } extern "C" int mkxp_cond_init(mkxp_cond_t *cond) { #ifndef MKXPZ_NO_STD_MUTEX *cond = new std::condition_variable_any; return 0; #elif defined(MKXPZ_DEVKITARM_NO_PTHREAD_H_MUTEX) CondVar_Init(cond); return 0; #elif !defined(MKXPZ_NO_PTHREAD_H_MUTEX) return pthread_cond_init(cond, NULL); #else return 0; #endif } extern "C" int mkxp_cond_destroy(mkxp_cond_t *cond) { #ifndef MKXPZ_NO_STD_MUTEX delete (std::condition_variable_any *)*cond; return 0; #elif defined(MKXPZ_DEVKITARM_NO_PTHREAD_H_MUTEX) return 0; #elif !defined(MKXPZ_NO_PTHREAD_H_MUTEX) return pthread_cond_destroy(cond); #else return 0; #endif } extern "C" int mkxp_cond_signal(mkxp_cond_t *cond) { #ifndef MKXPZ_NO_STD_MUTEX ((std::condition_variable_any *)*cond)->notify_one(); return 0; #elif defined(MKXPZ_DEVKITARM_NO_PTHREAD_H_MUTEX) CondVar_WakeUp(cond, 1); return 0; #elif !defined(MKXPZ_NO_PTHREAD_H_MUTEX) return pthread_cond_signal(cond); #else return 0; #endif } extern "C" int mkxp_cond_broadcast(mkxp_cond_t *cond) { #ifndef MKXPZ_NO_STD_MUTEX ((std::condition_variable_any *)*cond)->notify_all(); return 0; #elif defined(MKXPZ_DEVKITARM_NO_PTHREAD_H_MUTEX) CondVar_WakeUp(cond, -1); return 0; #elif !defined(MKXPZ_NO_PTHREAD_H_MUTEX) return pthread_cond_broadcast(cond); #else return 0; #endif } extern "C" int mkxp_cond_wait(mkxp_cond_t *cond, mkxp_mutex_t *mutex) { #ifndef MKXPZ_NO_STD_MUTEX if (mutex->recursive) { ((std::condition_variable_any *)*cond)->wait(*(std::recursive_mutex *)mutex->inner); } else { ((std::condition_variable_any *)*cond)->wait(*(std::mutex *)mutex->inner); } return 0; #elif defined(MKXPZ_DEVKITARM_NO_PTHREAD_H_MUTEX) if (mutex->recursive) { std::abort(); } CondVar_Wait(cond, &mutex->inner.light); return 0; #elif !defined(MKXPZ_NO_PTHREAD_H_MUTEX) return pthread_cond_wait(cond, mutex); #else return 0; #endif } extern "C" int mkxp_sem_init(mkxp_sem_t *sem, unsigned int value) { #ifndef MKXPZ_NO_SEMAPHORE_H return sem_init(sem, 0, value); #elif !defined(MKXPZ_NO_PTHREAD_H_MUTEX) *sem = (void *)new mkxp_sem_private; int mutex_init_result = mkxp_mutex_init(&((struct mkxp_sem_private *)*sem)->mutex, false); if (mutex_init_result) { return -1; } int cond_init_result = mkxp_cond_init(&((struct mkxp_sem_private *)*sem)->cond); if (cond_init_result) { mkxp_mutex_destroy(&((struct mkxp_sem_private *)*sem)->mutex); return -1; } ((struct mkxp_sem_private *)*sem)->value = value; return 0; #else *sem = value; return 0; #endif } extern "C" int mkxp_sem_destroy(mkxp_sem_t *sem) { #ifndef MKXPZ_NO_SEMAPHORE_H return sem_destroy(sem); #elif !defined(MKXPZ_NO_PTHREAD_H_MUTEX) mkxp_cond_destroy(&((struct mkxp_sem_private *)*sem)->cond); mkxp_mutex_destroy(&((struct mkxp_sem_private *)*sem)->mutex); delete (struct mkxp_sem_private *)*sem; return 0; #else return 0; #endif } extern "C" int mkxp_sem_post(mkxp_sem_t *sem) { #ifndef MKXPZ_NO_SEMAPHORE_H return sem_post(sem); #elif !defined(MKXPZ_NO_PTHREAD_H_MUTEX) while (mkxp_mutex_lock(&((struct mkxp_sem_private *)*sem)->mutex)) {} ++((struct mkxp_sem_private *)*sem)->value; mkxp_cond_signal(&((struct mkxp_sem_private *)*sem)->cond); mkxp_mutex_unlock(&((struct mkxp_sem_private *)*sem)->mutex); return 0; #else ++*sem; return 0; #endif } extern "C" int mkxp_sem_wait(mkxp_sem_t *sem) { #ifndef MKXPZ_NO_SEMAPHORE_H return sem_wait(sem); #elif !defined(MKXPZ_NO_PTHREAD_H_MUTEX) while (mkxp_mutex_lock(&((struct mkxp_sem_private *)*sem)->mutex)) {} for (;;) { if (((struct mkxp_sem_private *)*sem)->value) { --((struct mkxp_sem_private *)*sem)->value; mkxp_mutex_unlock(&((struct mkxp_sem_private *)*sem)->mutex); return 0; } while (mkxp_cond_wait(&((struct mkxp_sem_private *)*sem)->cond, &((struct mkxp_sem_private *)*sem)->mutex)) {} } #else assert(*sem); --*sem; return 0; #endif } extern "C" void mkxp_sleep_ms(uint32_t milliseconds) { #ifndef MKXPZ_NO_STD_THIS_THREAD_SLEEP_FOR std::this_thread::sleep_for(std::chrono::duration(milliseconds)); #elif !defined(MKXPZ_NO_USLEEP) usleep((useconds_t)1000 * (useconds_t)milliseconds); #elif !defined(MKXPZ_NO_NANOSLEEP) struct timespec t; t.tv_sec = milliseconds / 1000; t.tv_nsec = milliseconds % 1000; t.tv_nsec *= 1000000; nanosleep(&t, nullptr); #endif } #ifndef MKXPZ_NO_THREAD extern "C" int mkxp_thread_create(mkxp_thread_t *thread, void *(*func)(void *), void *arg) { #ifndef MKXPZ_NO_STD_THREAD *thread = new std::thread(func, arg); return 0; #elif defined(MKXPZ_DEVKITARM_NO_PTHREAD_H_THREAD) // Copied from https://github.com/libretro/libretro-common/blob/master/rthreads/ctr_pthread.h (licensed MIT) int32_t prio = 0; void *new_ctr_thread; int procnum = -2; /* use default cpu */ bool isNew3DS; APT_CheckNew3DS(&isNew3DS); if (isNew3DS) procnum = 2; if (!mutex_inited) { LightLock_Init(&safe_double_thread_launch); mutex_inited = true; } /* Must wait if attempting to launch 2 threads at once to prevent corruption of function pointer*/ while (LightLock_TryLock(&safe_double_thread_launch) != 0); svcGetThreadPriority(&prio, 0xFFFF8001); start_routine_jump = func; new_ctr_thread = threadCreate(ctr_thread_launcher, arg, 32 * 1024, prio - 1, procnum, false); if (!new_ctr_thread) { LightLock_Unlock(&safe_double_thread_launch); return EAGAIN; } *thread = new_ctr_thread; return 0; #elif !defined(MKXPZ_NO_PTHREAD_H_THREAD) return pthread_create(thread, nullptr, func, arg); #endif } extern "C" int mkxp_thread_join(mkxp_thread_t thread) { #ifndef MKXPZ_NO_STD_THREAD ((std::thread *)thread)->join(); delete (std::thread *)thread; return 0; #elif defined(MKXPZ_DEVKITARM_NO_PTHREAD_H_THREAD) // Copied from https://github.com/libretro/libretro-common/blob/master/rthreads/ctr_pthread.h (licensed MIT) /*retval is ignored*/ if (threadJoin(thread, (uint64_t)-1)) return -1; threadFree(thread); return 0; #elif !defined(MKXPZ_NO_PTHREAD_H_THREAD) return pthread_join(thread, nullptr); #endif } #endif extern "C" mkxp_thread_id_t mkxp_thread_self(void) { #ifndef MKXPZ_NO_STD_THREAD static_assert(sizeof(std::thread::id) <= sizeof(mkxp_thread_id_t), "`std::thread::id` is too big to fit in `mkxp_thread_id_t`"); mkxp_thread_id_t output = 0; std::thread::id input = std::this_thread::get_id(); std::memcpy(&output, &input, sizeof(std::thread::id)); return output; #elif defined(MKXPZ_DEVKITARM_NO_PTHREAD_H_THREAD) return threadGetCurrent(); #elif !defined(MKXPZ_NO_PTHREAD_H_THREAD) return pthread_self(); #else return 0; #endif }