mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-04-19 19:15:26 +00:00
util::dynamic_import
Futex implementation
This commit is contained in:
parent
8ad31d2559
commit
98fc131d47
3 changed files with 183 additions and 175 deletions
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 <typename F>
|
||||
struct dynamic_import
|
||||
{
|
||||
static_assert(sizeof(F) == 0, "Invalid function type");
|
||||
};
|
||||
|
||||
template <typename R, typename... Args>
|
||||
struct dynamic_import<R(Args...)>
|
||||
{
|
||||
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<R(*)(Args...)>(get_proc_address(lib, name));
|
||||
}
|
||||
|
||||
return ptr(args...);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#define DYNAMIC_IMPORT(lib, name, ...) static utils::dynamic_import<__VA_ARGS__> name(lib, #name);
|
||||
|
|
310
Utilities/sync.h
310
Utilities/sync.h
|
@ -4,185 +4,145 @@
|
|||
|
||||
#include "types.h"
|
||||
#include "Atomic.h"
|
||||
#include "dynamic_library.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
#define DYNAMIC_IMPORT(handle, name) do { name = reinterpret_cast<decltype(name)>(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<typename F>
|
||||
inline void keyed_wait(atomic_t<u32>& 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<u32>& 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 <time.h>
|
||||
#elif __linux__
|
||||
#include <errno.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <linux/futex.h>
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
#else
|
||||
#endif
|
||||
#include <ctime>
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <unordered_map>
|
||||
|
||||
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<int*, waiter*, pointer_hash<int>> map;
|
||||
|
||||
int operator()(int* uaddr, int futex_op, int val, const timespec* timeout, int*, uint val3)
|
||||
{
|
||||
std::unique_lock<std::mutex> 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
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue