305e3df8fa
The code in this file was adapted from the STL on the 2021-07-05. It backports the following Windows 8 functions to Windows 7: * WaitOnAddress * WakeByAddressSingle * WakeByAddressAll These functions are used within `til`. This commit will allow `til` to be used in the conhost source code. Validation * [x] correct .dll loads on Windows 7 * [x] correct .dll loads on Windows 10 * [x] link line for PublicTerminalCore prefers this fake apiset over kernel32
190 lines
6 KiB
C++
190 lines
6 KiB
C++
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT license.
|
|
|
|
//
|
|
// The code in this file was adapted from the STL on the 2021-07-05. Commit:
|
|
// https://github.com/microsoft/STL/blob/e745bad3b1d05b5b19ec652d68abb37865ffa454/stl/src/atomic_wait.cpp
|
|
//
|
|
// It backports the following Windows 8 functions to Windows 7:
|
|
// * WaitOnAddress
|
|
// * WakeByAddressSingle
|
|
// * WakeByAddressAll
|
|
//
|
|
|
|
#include <cstdint>
|
|
#include <new>
|
|
|
|
#include <winsdkver.h>
|
|
#define _WIN32_WINNT 0x0601
|
|
#include <sdkddkver.h>
|
|
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#define NOMINMAX
|
|
#include <Windows.h>
|
|
#include <intrin.h>
|
|
|
|
namespace
|
|
{
|
|
class [[nodiscard]] SRWLockGuard
|
|
{
|
|
public:
|
|
explicit SRWLockGuard(SRWLOCK & lock) noexcept :
|
|
_lock(&lock)
|
|
{
|
|
AcquireSRWLockExclusive(_lock);
|
|
}
|
|
|
|
~SRWLockGuard()
|
|
{
|
|
ReleaseSRWLockExclusive(_lock);
|
|
}
|
|
|
|
SRWLockGuard(const SRWLockGuard&) = delete;
|
|
SRWLockGuard& operator=(const SRWLockGuard&) = delete;
|
|
|
|
SRWLockGuard(SRWLockGuard &&) = delete;
|
|
SRWLockGuard& operator=(SRWLockGuard&&) = delete;
|
|
|
|
private:
|
|
SRWLOCK* _lock;
|
|
};
|
|
|
|
struct WaitContext
|
|
{
|
|
const volatile void* address;
|
|
WaitContext* next;
|
|
WaitContext* prev;
|
|
CONDITION_VARIABLE cv;
|
|
};
|
|
|
|
struct [[nodiscard]] GuardedWaitContext : WaitContext
|
|
{
|
|
GuardedWaitContext(const volatile void* storage, WaitContext* head) noexcept :
|
|
WaitContext{ storage, head, head->prev, CONDITION_VARIABLE_INIT }
|
|
{
|
|
prev->next = this;
|
|
next->prev = this;
|
|
}
|
|
|
|
~GuardedWaitContext()
|
|
{
|
|
const auto n = next;
|
|
const auto p = prev;
|
|
next->prev = p;
|
|
prev->next = n;
|
|
}
|
|
|
|
GuardedWaitContext(const GuardedWaitContext&) = delete;
|
|
GuardedWaitContext& operator=(const GuardedWaitContext&) = delete;
|
|
|
|
GuardedWaitContext(GuardedWaitContext &&) = delete;
|
|
GuardedWaitContext& operator=(GuardedWaitContext&&) = delete;
|
|
};
|
|
|
|
#pragma warning(push)
|
|
#pragma warning(disable : 4324) // structure was padded due to alignment specifier
|
|
struct alignas(std::hardware_destructive_interference_size) WaitTableEntry
|
|
{
|
|
SRWLOCK lock = SRWLOCK_INIT;
|
|
WaitContext head = { nullptr, &head, &head, CONDITION_VARIABLE_INIT };
|
|
};
|
|
#pragma warning(pop)
|
|
|
|
[[nodiscard]] WaitTableEntry& GetWaitTableEntry(const volatile void* const storage) noexcept
|
|
{
|
|
// A prime number for the hash table size was chosen to prevent collisions.
|
|
constexpr size_t size = 251;
|
|
constexpr std::hash<uintptr_t> hasher;
|
|
|
|
static WaitTableEntry table[size];
|
|
#pragma warning(suppress : 26446) // Prefer to use gsl::at() instead of unchecked subscript operator
|
|
#pragma warning(suppress : 26482) // Only index into arrays using constant expressions
|
|
#pragma warning(suppress : 26490) // Don't use reinterpret_cast
|
|
return table[hasher(reinterpret_cast<uintptr_t>(storage)) % size];
|
|
}
|
|
|
|
#pragma warning(suppress : 26429) // Symbol 'comparand' is never tested for nullness, it can be marked as not_null
|
|
bool AreEqual(const volatile void* storage, const void* comparand, size_t size) noexcept
|
|
{
|
|
switch (size)
|
|
{
|
|
case 1:
|
|
return __iso_volatile_load8(static_cast<const volatile __int8*>(storage)) == *static_cast<const __int8*>(comparand);
|
|
case 2:
|
|
return __iso_volatile_load16(static_cast<const volatile __int16*>(storage)) == *static_cast<const __int16*>(comparand);
|
|
case 4:
|
|
return __iso_volatile_load32(static_cast<const volatile __int32*>(storage)) == *static_cast<const __int32*>(comparand);
|
|
case 8:
|
|
return __iso_volatile_load64(static_cast<const volatile __int64*>(storage)) == *static_cast<const __int64*>(comparand);
|
|
default:
|
|
abort();
|
|
}
|
|
}
|
|
} // unnamed namespace
|
|
|
|
extern "C" BOOL WINAPI WaitOnAddress(_In_reads_bytes_(AddressSize) volatile VOID* Address, _In_reads_bytes_(AddressSize) PVOID CompareAddress, _In_ SIZE_T AddressSize, _In_opt_ DWORD dwMilliseconds)
|
|
{
|
|
auto& entry = GetWaitTableEntry(Address);
|
|
|
|
SRWLockGuard guard{ entry.lock };
|
|
GuardedWaitContext context{ Address, &entry.head };
|
|
|
|
for (;;)
|
|
{
|
|
// NOTE: under lock to prevent lost wakes
|
|
if (!AreEqual(Address, CompareAddress, AddressSize))
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
if (!SleepConditionVariableSRW(&context.cv, &entry.lock, dwMilliseconds, 0))
|
|
{
|
|
#ifndef NDEBUG
|
|
if (GetLastError() != ERROR_TIMEOUT)
|
|
{
|
|
abort();
|
|
}
|
|
#endif
|
|
return FALSE;
|
|
}
|
|
|
|
if (dwMilliseconds != INFINITE)
|
|
{
|
|
// spurious wake to recheck the clock
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
extern "C" VOID WINAPI WakeByAddressSingle(_In_ PVOID Address)
|
|
{
|
|
auto& entry = GetWaitTableEntry(Address);
|
|
SRWLockGuard guard(entry.lock);
|
|
|
|
for (auto context = entry.head.next; context != &entry.head; context = context->next)
|
|
{
|
|
if (context->address == Address)
|
|
{
|
|
// Can't move wake outside SRWLOCKed section: SRWLOCK also protects the context itself
|
|
WakeAllConditionVariable(&context->cv);
|
|
// This break; is the difference between WakeByAddressSingle and WakeByAddressAll
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
extern "C" VOID WINAPI WakeByAddressAll(_In_ PVOID Address)
|
|
{
|
|
auto& entry = GetWaitTableEntry(Address);
|
|
SRWLockGuard guard(entry.lock);
|
|
|
|
for (auto context = entry.head.next; context != &entry.head; context = context->next)
|
|
{
|
|
if (context->address == Address)
|
|
{
|
|
// Can't move wake outside SRWLOCKed section: SRWLOCK also protects the context itself
|
|
WakeAllConditionVariable(&context->cv);
|
|
}
|
|
}
|
|
}
|