util::dynamic_import

Futex implementation
This commit is contained in:
Nekotekina 2017-01-24 16:52:15 +03:00 committed by Ivan
parent 8ad31d2559
commit 98fc131d47
3 changed files with 183 additions and 175 deletions

View file

@ -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
}
}

View file

@ -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);

View file

@ -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
}