mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-07-05 14:31:24 +12: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();
|
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;
|
bool loaded() const;
|
||||||
explicit operator bool() 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 "types.h"
|
||||||
#include "Atomic.h"
|
#include "Atomic.h"
|
||||||
|
#include "dynamic_library.h"
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
|
#include <time.h>
|
||||||
#define DYNAMIC_IMPORT(handle, name) do { name = reinterpret_cast<decltype(name)>(GetProcAddress(handle, #name)); } while (0)
|
#elif __linux__
|
||||||
|
#include <errno.h>
|
||||||
static NTSTATUS(*NtSetTimerResolution)(ULONG DesiredResolution, BOOLEAN SetResolution, PULONG CurrentResolution);
|
#include <sys/syscall.h>
|
||||||
static NTSTATUS(*NtWaitForKeyedEvent)(HANDLE Handle, PVOID Key, BOOLEAN Alertable, PLARGE_INTEGER Timeout);
|
#include <linux/futex.h>
|
||||||
static NTSTATUS(*NtReleaseKeyedEvent)(HANDLE Handle, PVOID Key, BOOLEAN Alertable, PLARGE_INTEGER Timeout);
|
#include <sys/time.h>
|
||||||
|
#include <unistd.h>
|
||||||
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();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
#endif
|
||||||
|
#include <ctime>
|
||||||
|
#include <chrono>
|
||||||
|
#include <mutex>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
namespace util
|
#ifdef _WIN32
|
||||||
{
|
DYNAMIC_IMPORT("ntdll.dll", NtSetTimerResolution, NTSTATUS(ULONG DesiredResolution, BOOLEAN SetResolution, PULONG CurrentResolution));
|
||||||
struct native_rwlock;
|
DYNAMIC_IMPORT("ntdll.dll", NtWaitForKeyedEvent, NTSTATUS(HANDLE Handle, PVOID Key, BOOLEAN Alertable, PLARGE_INTEGER Timeout));
|
||||||
struct native_cond;
|
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
|
#endif
|
||||||
|
|
||||||
CHECK_SIZE_ALIGN(util::native_rwlock, sizeof(void*), alignof(void*));
|
#ifndef __linux__
|
||||||
CHECK_SIZE_ALIGN(util::native_cond, sizeof(void*), alignof(void*));
|
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
Add a link
Reference in a new issue