2019-07-27 00:34:10 +02:00
|
|
|
|
#include "atomic.hpp"
|
|
|
|
|
|
|
|
|
|
|
|
#include "Utilities/sync.h"
|
|
|
|
|
|
|
2019-07-29 14:12:20 +02:00
|
|
|
|
// Should be at least 65536, currently 2097152.
|
|
|
|
|
|
static constexpr std::uintptr_t s_hashtable_size = 1u << 21;
|
2019-07-27 00:34:10 +02:00
|
|
|
|
|
|
|
|
|
|
// TODO: it's probably better to implement more effective futex emulation for OSX/BSD here.
|
|
|
|
|
|
static atomic_t<s64> s_hashtable[s_hashtable_size];
|
|
|
|
|
|
|
2019-07-29 19:28:20 +02:00
|
|
|
|
// Max number of waiters (16383)
|
|
|
|
|
|
static constexpr s64 s_waiter_mask = 0x3fff;
|
|
|
|
|
|
|
|
|
|
|
|
// Implementation detail (remaining bits out of 32)
|
|
|
|
|
|
static constexpr s64 s_signal_mask = 0xffffffff & ~s_waiter_mask;
|
|
|
|
|
|
|
2019-07-27 00:34:10 +02:00
|
|
|
|
static inline bool ptr_cmp(const void* data, std::size_t size, u64 old_value)
|
|
|
|
|
|
{
|
|
|
|
|
|
switch (size)
|
|
|
|
|
|
{
|
|
|
|
|
|
case 1: return reinterpret_cast<const atomic_t<u8>*>(data)->load() == old_value;
|
|
|
|
|
|
case 2: return reinterpret_cast<const atomic_t<u16>*>(data)->load() == old_value;
|
|
|
|
|
|
case 4: return reinterpret_cast<const atomic_t<u32>*>(data)->load() == old_value;
|
|
|
|
|
|
case 8: return reinterpret_cast<const atomic_t<u64>*>(data)->load() == old_value;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void atomic_storage_futex::wait(const void* data, std::size_t size, u64 old_value)
|
|
|
|
|
|
{
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
|
|
if (OptWaitOnAddress)
|
|
|
|
|
|
{
|
|
|
|
|
|
OptWaitOnAddress(const_cast<volatile void*>(data), &old_value, size, INFINITE);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
const std::intptr_t iptr = reinterpret_cast<std::intptr_t>(data);
|
|
|
|
|
|
|
2019-07-29 14:12:20 +02:00
|
|
|
|
atomic_t<s64>& entry = s_hashtable[iptr % s_hashtable_size];
|
2019-07-27 00:34:10 +02:00
|
|
|
|
|
|
|
|
|
|
u32 new_value = 0;
|
|
|
|
|
|
|
|
|
|
|
|
const auto [_, ok] = entry.fetch_op([&](s64& value)
|
|
|
|
|
|
{
|
2019-07-29 19:28:20 +02:00
|
|
|
|
if ((value & s_waiter_mask) == s_waiter_mask || (value & s_signal_mask) == s_signal_mask)
|
2019-07-27 00:34:10 +02:00
|
|
|
|
{
|
|
|
|
|
|
// Return immediately on waiter overflow or signal overflow
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!value || (value >> 32) == (iptr >> 16))
|
|
|
|
|
|
{
|
|
|
|
|
|
// Store 32 highest bits of signed 48-bit pointer
|
|
|
|
|
|
value |= (iptr >> 16) * 0x1'0000'0000;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// Zero highest bits (collision)
|
|
|
|
|
|
value &= 0xffffffff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-07-29 19:28:20 +02:00
|
|
|
|
new_value = static_cast<u32>(value += 1);
|
2019-07-27 00:34:10 +02:00
|
|
|
|
return true;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (!ok)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (ptr_cmp(data, size, old_value))
|
|
|
|
|
|
{
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
|
|
NtWaitForKeyedEvent(nullptr, &entry, false, nullptr);
|
|
|
|
|
|
return;
|
|
|
|
|
|
#else
|
|
|
|
|
|
futex(reinterpret_cast<char*>(&entry) + 4 * IS_BE_MACHINE, FUTEX_WAIT_PRIVATE, new_value, nullptr);
|
|
|
|
|
|
#endif
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
while (true)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Try to decrement
|
|
|
|
|
|
const auto [prev, ok] = entry.fetch_op([&](s64& value)
|
|
|
|
|
|
{
|
2019-07-29 19:28:20 +02:00
|
|
|
|
if (value & s_waiter_mask)
|
2019-07-27 00:34:10 +02:00
|
|
|
|
{
|
2019-07-29 19:28:20 +02:00
|
|
|
|
value -= 1;
|
2019-07-27 00:34:10 +02:00
|
|
|
|
|
2019-07-29 19:28:20 +02:00
|
|
|
|
if ((value & s_waiter_mask) == 0)
|
2019-07-27 00:34:10 +02:00
|
|
|
|
{
|
|
|
|
|
|
// Reset on last waiter
|
|
|
|
|
|
value = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (ok)
|
|
|
|
|
|
{
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
|
|
static LARGE_INTEGER instant{};
|
|
|
|
|
|
|
|
|
|
|
|
if (!NtWaitForKeyedEvent(nullptr, &entry, false, &instant))
|
|
|
|
|
|
{
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
#else
|
|
|
|
|
|
// Unreachable
|
|
|
|
|
|
std::terminate();
|
|
|
|
|
|
#endif
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void atomic_storage_futex::notify_one(const void* data)
|
|
|
|
|
|
{
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
|
|
if (OptWaitOnAddress)
|
|
|
|
|
|
{
|
|
|
|
|
|
OptWakeByAddressSingle(const_cast<void*>(data));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
const std::intptr_t iptr = reinterpret_cast<std::intptr_t>(data);
|
|
|
|
|
|
|
2019-07-29 14:12:20 +02:00
|
|
|
|
atomic_t<s64>& entry = s_hashtable[iptr % s_hashtable_size];
|
2019-07-27 00:34:10 +02:00
|
|
|
|
|
|
|
|
|
|
const auto [prev, ok] = entry.fetch_op([&](s64& value)
|
|
|
|
|
|
{
|
2019-07-29 19:28:20 +02:00
|
|
|
|
if (value & s_waiter_mask && (value >> 32) == (iptr >> 16))
|
2019-07-27 00:34:10 +02:00
|
|
|
|
{
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
|
|
// Try to decrement if no collision
|
2019-07-29 19:28:20 +02:00
|
|
|
|
value -= 1;
|
2019-07-27 00:34:10 +02:00
|
|
|
|
|
2019-07-29 19:28:20 +02:00
|
|
|
|
if ((value & s_waiter_mask) == 0)
|
2019-07-27 00:34:10 +02:00
|
|
|
|
{
|
|
|
|
|
|
// Reset on last waiter
|
|
|
|
|
|
value = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
#else
|
2019-07-29 19:28:20 +02:00
|
|
|
|
if ((value & s_signal_mask) == s_signal_mask)
|
2019-07-27 00:34:10 +02:00
|
|
|
|
{
|
|
|
|
|
|
// Signal overflow, do nothing
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-07-29 19:28:20 +02:00
|
|
|
|
value += s_signal_mask & -s_signal_mask;
|
2019-07-27 00:34:10 +02:00
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (ok)
|
|
|
|
|
|
{
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
|
|
NtReleaseKeyedEvent(nullptr, &entry, false, nullptr);
|
|
|
|
|
|
return;
|
|
|
|
|
|
#else
|
|
|
|
|
|
futex(reinterpret_cast<char*>(&entry) + 4 * IS_BE_MACHINE, FUTEX_WAKE_PRIVATE, 1);
|
|
|
|
|
|
return;
|
|
|
|
|
|
#endif
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (prev)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Collision, notify everything
|
|
|
|
|
|
notify_all(data);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void atomic_storage_futex::notify_all(const void* data)
|
|
|
|
|
|
{
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
|
|
if (OptWaitOnAddress)
|
|
|
|
|
|
{
|
|
|
|
|
|
OptWakeByAddressAll(const_cast<void*>(data));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
const std::intptr_t iptr = reinterpret_cast<std::intptr_t>(data);
|
|
|
|
|
|
|
2019-07-29 14:12:20 +02:00
|
|
|
|
atomic_t<s64>& entry = s_hashtable[iptr % s_hashtable_size];
|
2019-07-27 00:34:10 +02:00
|
|
|
|
|
|
|
|
|
|
// Consume everything
|
|
|
|
|
|
#ifdef _WIN32
|
2019-07-29 19:28:20 +02:00
|
|
|
|
for (s64 count = entry.exchange(0) & s_waiter_mask; count; count--)
|
2019-07-27 00:34:10 +02:00
|
|
|
|
{
|
|
|
|
|
|
NtReleaseKeyedEvent(nullptr, &entry, false, nullptr);
|
|
|
|
|
|
}
|
|
|
|
|
|
#else
|
|
|
|
|
|
const auto [_, ok] = entry.fetch_op([&](s64& value)
|
|
|
|
|
|
{
|
2019-07-29 19:28:20 +02:00
|
|
|
|
if (value & s_waiter_mask)
|
2019-07-27 00:34:10 +02:00
|
|
|
|
{
|
2019-07-29 19:28:20 +02:00
|
|
|
|
if ((value & s_signal_mask) == s_signal_mask)
|
2019-07-27 00:34:10 +02:00
|
|
|
|
{
|
|
|
|
|
|
// Signal overflow, do nothing
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-07-29 19:28:20 +02:00
|
|
|
|
value += s_signal_mask & -s_signal_mask;
|
2019-07-27 00:34:10 +02:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (ok)
|
|
|
|
|
|
{
|
|
|
|
|
|
futex(reinterpret_cast<char*>(&entry) + 4 * IS_BE_MACHINE, FUTEX_WAKE_PRIVATE, 0x7fffffff);
|
|
|
|
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
|
|
}
|