diff --git a/Utilities/dynamic_library.cpp b/Utilities/dynamic_library.cpp index 68ec5066c4..112b44be5e 100644 --- a/Utilities/dynamic_library.cpp +++ b/Utilities/dynamic_library.cpp @@ -57,4 +57,13 @@ namespace utils { return loaded(); } + + void* get_proc_address(const char* lib, const char* name) + { +#ifdef _WIN32 + return GetProcAddress(GetModuleHandleA(lib), name); +#else + return dlsym(dlopen(lib, RTLD_NOLOAD), name); +#endif + } } diff --git a/Utilities/dynamic_library.h b/Utilities/dynamic_library.h index d5578d08e0..35dd2619d6 100644 --- a/Utilities/dynamic_library.h +++ b/Utilities/dynamic_library.h @@ -38,4 +38,43 @@ namespace utils bool loaded() const; explicit operator bool() const; }; + + // (assume the lib is always loaded) + void* get_proc_address(const char* lib, const char* name); + + template + struct dynamic_import + { + static_assert(sizeof(F) == 0, "Invalid function type"); + }; + + template + struct dynamic_import + { + R(*ptr)(Args...); + const char* const lib; + const char* const name; + + // Constant initialization + constexpr dynamic_import(const char* lib, const char* name) + : ptr(nullptr) + , lib(lib) + , name(name) + { + } + + // Caller + R operator()(Args... args) + { + if (!ptr) + { + // TODO: atomic + ptr = reinterpret_cast(get_proc_address(lib, name)); + } + + return ptr(args...); + } + }; } + +#define DYNAMIC_IMPORT(lib, name, ...) static utils::dynamic_import<__VA_ARGS__> name(lib, #name); diff --git a/Utilities/sync.h b/Utilities/sync.h index 98fa4d8377..7f74bd9abd 100644 --- a/Utilities/sync.h +++ b/Utilities/sync.h @@ -4,185 +4,145 @@ #include "types.h" #include "Atomic.h" +#include "dynamic_library.h" #ifdef _WIN32 - #include - -#define DYNAMIC_IMPORT(handle, name) do { name = reinterpret_cast(GetProcAddress(handle, #name)); } while (0) - -static NTSTATUS(*NtSetTimerResolution)(ULONG DesiredResolution, BOOLEAN SetResolution, PULONG CurrentResolution); -static NTSTATUS(*NtWaitForKeyedEvent)(HANDLE Handle, PVOID Key, BOOLEAN Alertable, PLARGE_INTEGER Timeout); -static NTSTATUS(*NtReleaseKeyedEvent)(HANDLE Handle, PVOID Key, BOOLEAN Alertable, PLARGE_INTEGER Timeout); - -namespace util -{ - static const bool keyed_init = [] - { - const auto handle = LoadLibraryA("ntdll.dll"); - DYNAMIC_IMPORT(handle, NtSetTimerResolution); - DYNAMIC_IMPORT(handle, NtWaitForKeyedEvent); - DYNAMIC_IMPORT(handle, NtReleaseKeyedEvent); - FreeLibrary(handle); - - ULONG res = 100; - NtSetTimerResolution(100, TRUE, &res); - - return NtWaitForKeyedEvent && NtReleaseKeyedEvent; - }(); - - // Wait for specified condition. func() acknowledges success by value modification. - template - inline void keyed_wait(atomic_t& key, F&& func) - { - while (true) - { - u32 read = key.load(); - u32 copy = read; - - while (func(read), read != copy) - { - read = key.compare_and_swap(copy, read); - - if (copy == read) - { - return; - } - - copy = read; - } - - NtWaitForKeyedEvent(NULL, &key, FALSE, NULL); - } - } - - // Try to wake up a thread. - inline bool keyed_post(atomic_t& key, u32 acknowledged_value) - { - LARGE_INTEGER timeout; - timeout.QuadPart = -50; - - while (UNLIKELY(NtReleaseKeyedEvent(NULL, &key, FALSE, &timeout) != ERROR_SUCCESS)) - { - if (key.load() != acknowledged_value) - return false; - } - - return true; - } - - struct native_rwlock - { - SRWLOCK rwlock = SRWLOCK_INIT; - - constexpr native_rwlock() = default; - - native_rwlock(const native_rwlock&) = delete; - - void lock() - { - AcquireSRWLockExclusive(&rwlock); - } - - bool try_lock() - { - return TryAcquireSRWLockExclusive(&rwlock) != 0; - } - - void unlock() - { - ReleaseSRWLockExclusive(&rwlock); - } - - void lock_shared() - { - AcquireSRWLockShared(&rwlock); - } - - bool try_lock_shared() - { - return TryAcquireSRWLockShared(&rwlock) != 0; - } - - void unlock_shared() - { - ReleaseSRWLockShared(&rwlock); - } - }; - - struct native_cond - { - CONDITION_VARIABLE cond = CONDITION_VARIABLE_INIT; - - constexpr native_cond() = default; - - native_cond(const native_cond&) = delete; - - void notify_one() - { - WakeConditionVariable(&cond); - } - - void notify_all() - { - WakeAllConditionVariable(&cond); - } - - void wait(native_rwlock& rwlock) - { - SleepConditionVariableSRW(&cond, &rwlock.rwlock, INFINITE, 0); - } - - void wait_shared(native_rwlock& rwlock) - { - SleepConditionVariableSRW(&cond, &rwlock.rwlock, INFINITE, CONDITION_VARIABLE_LOCKMODE_SHARED); - } - }; - - class exclusive_lock - { - native_rwlock& m_rwlock; - - public: - exclusive_lock(native_rwlock& rwlock) - : m_rwlock(rwlock) - { - m_rwlock.lock(); - } - - ~exclusive_lock() - { - m_rwlock.unlock(); - } - }; - - class shared_lock - { - native_rwlock& m_rwlock; - - public: - shared_lock(native_rwlock& rwlock) - : m_rwlock(rwlock) - { - m_rwlock.lock_shared(); - } - - ~shared_lock() - { - m_rwlock.unlock_shared(); - } - }; -} - +#include +#elif __linux__ +#include +#include +#include +#include +#include #else +#endif +#include +#include +#include +#include +#include -namespace util -{ - struct native_rwlock; - struct native_cond; -} - +#ifdef _WIN32 +DYNAMIC_IMPORT("ntdll.dll", NtSetTimerResolution, NTSTATUS(ULONG DesiredResolution, BOOLEAN SetResolution, PULONG CurrentResolution)); +DYNAMIC_IMPORT("ntdll.dll", NtWaitForKeyedEvent, NTSTATUS(HANDLE Handle, PVOID Key, BOOLEAN Alertable, PLARGE_INTEGER Timeout)); +DYNAMIC_IMPORT("ntdll.dll", NtReleaseKeyedEvent, NTSTATUS(HANDLE Handle, PVOID Key, BOOLEAN Alertable, PLARGE_INTEGER Timeout)); +DYNAMIC_IMPORT("ntdll.dll", NtDelayExecution, NTSTATUS(BOOLEAN Alertable, PLARGE_INTEGER DelayInterval)); #endif -CHECK_SIZE_ALIGN(util::native_rwlock, sizeof(void*), alignof(void*)); -CHECK_SIZE_ALIGN(util::native_cond, sizeof(void*), alignof(void*)); +#ifndef __linux__ +enum +{ + FUTEX_PRIVATE_FLAG = 0, + FUTEX_WAIT = 0, + FUTEX_WAIT_PRIVATE = FUTEX_WAIT, + FUTEX_WAKE = 1, + FUTEX_WAKE_PRIVATE = FUTEX_WAKE, + FUTEX_BITSET = 2, + FUTEX_WAIT_BITSET = FUTEX_WAIT | FUTEX_BITSET, + FUTEX_WAIT_BITSET_PRIVATE = FUTEX_WAIT_BITSET, + FUTEX_WAKE_BITSET = FUTEX_WAKE | FUTEX_BITSET, + FUTEX_WAKE_BITSET_PRIVATE = FUTEX_WAKE_BITSET, +}; +#endif + +inline int futex(int* uaddr, int futex_op, int val, const timespec* timeout, int* uaddr2, int val3) +{ +#ifdef __linux__ + return syscall(SYS_futex, uaddr, futex_op, val, timeout, uaddr, val3); +#else + static struct futex_map + { + struct waiter + { + int val; + uint mask; + std::condition_variable cv; + }; + + std::mutex mutex; + std::unordered_multimap> map; + + int operator()(int* uaddr, int futex_op, int val, const timespec* timeout, int*, uint val3) + { + std::unique_lock lock(mutex); + + switch (futex_op) + { + case FUTEX_WAIT: + { + val3 = -1; + // Fallthrough + } + case FUTEX_WAIT_BITSET: + { + if (*(volatile int*)uaddr != val) + { + errno = EAGAIN; + return -1; + } + + waiter rec; + rec.val = val; + rec.mask = val3; + const auto& ref = *map.emplace(uaddr, &rec); + + int res = 0; + + if (!timeout) + { + rec.cv.wait(lock, [&] { return !rec.mask; }); + } + else if (futex_op == FUTEX_WAIT) + { + const auto nsec = std::chrono::nanoseconds(timeout->tv_nsec + timeout->tv_sec * 1000000000ull); + + if (!rec.cv.wait_for(lock, nsec, [&] { return !rec.mask; })) + { + res = -1; + errno = ETIMEDOUT; + } + } + else + { + // TODO + } + + map.erase(std::find(map.find(uaddr), map.end(), ref)); + return res; + } + + case FUTEX_WAKE: + { + val3 = -1; + // Fallthrough + } + case FUTEX_WAKE_BITSET: + { + int res = 0; + + for (auto range = map.equal_range(uaddr); val && range.first != range.second; range.first++) + { + auto& entry = *range.first->second; + + if (entry.mask & val3) + { + entry.cv.notify_one(); + entry.mask = 0; + res++; + val--; + } + } + + return res; + } + } + + errno = EINVAL; + return -1; + } + } g_futex; + + return g_futex(uaddr, futex_op, val, timeout, uaddr2, val3); +#endif +}