* Remote dealloc refactor. * Improve remote dealloc Change remote to count down to 0, so fast path does not need a constant. Use signed value so that branch does not depend on addition. * Inline remote_dealloc The fast path of remote_dealloc is sufficiently compact that it can be inlined. * Improve fast path in Slab::alloc Turn the internal structure into tail calls, to improve fast path. Should be no algorithmic changes. * Refactor initialisation to help fast path. Break lazy initialisation into two functions, so it is easier to codegen fast paths. * Minor tidy to statically sized dealloc. * Refactor semi-slow path for alloc Make the backup path a bit faster. Only algorithmic change is to delay checking for first allocation. Otherwise, should be unchanged. * Test initial operation of a thread The first operation a new thread takes is special. It results in allocating an allocator, and swinging it into the TLS. This makes this a very special path, that is rarely tested. This test generates a lot of threads to cover the first alloc and dealloc operations. * Correctly handle reusing get_noncachable * Fix large alloc stats Large alloc stats aren't necessarily balanced on a thread, this changes to tracking individual pushs and pops, rather than the net effect (with an unsigned value). * Fix TLS init on large alloc path * Add Bump ptrs to allocator Each allocator has a bump ptr for each size class. This is no longer slab local. Slabs that haven't been fully allocated no longer need to be in the DLL for this sizeclass. * Change to a cycle non-empty list This change reduces the branching in the case of finding a new free list. Using a non-empty cyclic list enables branch free add, and a single branch in remove to detect the empty case. * Update differences * Rename first allocation Use needs initialisation as makes more sense for other scenarios. * Use a ptrdiff to help with zero init. * Make GlobalPlaceholder zero init The GlobalPlaceholder allocator is now a zero init block of memory. This removes various issues for when things are initialised. It is made read-only to we detect write to it on some platforms.
236 lines
6.4 KiB
C++
236 lines
6.4 KiB
C++
#pragma once
|
|
|
|
#include "../ds/address.h"
|
|
#include "../ds/bits.h"
|
|
#include "../mem/allocconfig.h"
|
|
|
|
#ifdef _WIN32
|
|
# ifndef _MSC_VER
|
|
# include <cstdio>
|
|
# endif
|
|
# define WIN32_LEAN_AND_MEAN
|
|
# ifndef NOMINMAX
|
|
# define NOMINMAX
|
|
# endif
|
|
# include <windows.h>
|
|
// VirtualAlloc2 is exposed in RS5 headers.
|
|
# ifdef NTDDI_WIN10_RS5
|
|
# if (NTDDI_VERSION >= NTDDI_WIN10_RS5) && \
|
|
(WINVER >= _WIN32_WINNT_WIN10) && !defined(USE_SYSTEMATIC_TESTING)
|
|
# define PLATFORM_HAS_VIRTUALALLOC2
|
|
# endif
|
|
# endif
|
|
|
|
namespace snmalloc
|
|
{
|
|
class PALWindows
|
|
{
|
|
/**
|
|
* A flag indicating that we have tried to register for low-memory
|
|
* notifications.
|
|
*/
|
|
static inline std::atomic<bool> registered_for_notifications;
|
|
static inline HANDLE lowMemoryObject;
|
|
|
|
/**
|
|
* List of callbacks for low-memory notification
|
|
*/
|
|
static inline PalNotifier low_memory_callbacks;
|
|
|
|
/**
|
|
* Callback, used when the system delivers a low-memory notification. This
|
|
* calls all the handlers registered with the PAL.
|
|
*/
|
|
static void CALLBACK low_memory(_In_ PVOID, _In_ BOOLEAN)
|
|
{
|
|
low_memory_callbacks.notify_all();
|
|
}
|
|
|
|
public:
|
|
PALWindows()
|
|
{
|
|
// No error handling here - if this doesn't work, then we will just
|
|
// consume more memory. There's nothing sensible that we could do in
|
|
// error handling. We also leak both the low memory notification object
|
|
// handle and the wait object handle. We'll need them until the program
|
|
// exits, so there's little point doing anything else.
|
|
//
|
|
// We only try to register once. If this fails, give up. Even if we
|
|
// create multiple PAL objects, we don't want to get more than one
|
|
// callback.
|
|
if (!registered_for_notifications.exchange(true))
|
|
{
|
|
lowMemoryObject =
|
|
CreateMemoryResourceNotification(LowMemoryResourceNotification);
|
|
HANDLE waitObject;
|
|
RegisterWaitForSingleObject(
|
|
&waitObject,
|
|
lowMemoryObject,
|
|
low_memory,
|
|
nullptr,
|
|
INFINITE,
|
|
WT_EXECUTEDEFAULT);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Bitmap of PalFeatures flags indicating the optional features that this
|
|
* PAL supports. This PAL supports low-memory notifications.
|
|
*/
|
|
static constexpr uint64_t pal_features = LowMemoryNotification
|
|
# if defined(PLATFORM_HAS_VIRTUALALLOC2)
|
|
| AlignedAllocation
|
|
# endif
|
|
;
|
|
|
|
/**
|
|
* Check whether the low memory state is still in effect. This is an
|
|
* expensive operation and should not be on any fast paths.
|
|
*/
|
|
bool expensive_low_memory_check()
|
|
{
|
|
BOOL result;
|
|
QueryMemoryResourceNotification(lowMemoryObject, &result);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Register callback object for low-memory notifications.
|
|
* Client is responsible for allocation, and ensuring the object is live
|
|
* for the duration of the program.
|
|
*/
|
|
static void
|
|
register_for_low_memory_callback(PalNotificationObject* callback)
|
|
{
|
|
low_memory_callbacks.register_notification(callback);
|
|
}
|
|
|
|
static void error(const char* const str)
|
|
{
|
|
puts(str);
|
|
fflush(stdout);
|
|
abort();
|
|
}
|
|
|
|
/// Notify platform that we will not be using these pages
|
|
void notify_not_using(void* p, size_t size) noexcept
|
|
{
|
|
SNMALLOC_ASSERT(is_aligned_block<OS_PAGE_SIZE>(p, size));
|
|
|
|
BOOL ok = VirtualFree(p, size, MEM_DECOMMIT);
|
|
|
|
if (!ok)
|
|
error("VirtualFree failed");
|
|
}
|
|
|
|
/// Notify platform that we will be using these pages
|
|
template<ZeroMem zero_mem>
|
|
void notify_using(void* p, size_t size) noexcept
|
|
{
|
|
SNMALLOC_ASSERT(
|
|
is_aligned_block<OS_PAGE_SIZE>(p, size) || (zero_mem == NoZero));
|
|
|
|
void* r = VirtualAlloc(p, size, MEM_COMMIT, PAGE_READWRITE);
|
|
|
|
if (r == nullptr)
|
|
error("out of memory");
|
|
}
|
|
|
|
/// OS specific function for zeroing memory
|
|
template<bool page_aligned = false>
|
|
void zero(void* p, size_t size) noexcept
|
|
{
|
|
if (page_aligned || is_aligned_block<OS_PAGE_SIZE>(p, size))
|
|
{
|
|
SNMALLOC_ASSERT(is_aligned_block<OS_PAGE_SIZE>(p, size));
|
|
notify_not_using(p, size);
|
|
notify_using<YesZero>(p, size);
|
|
}
|
|
else
|
|
::memset(p, 0, size);
|
|
}
|
|
|
|
# ifdef USE_SYSTEMATIC_TESTING
|
|
size_t& systematic_bump_ptr()
|
|
{
|
|
static size_t bump_ptr = (size_t)0x4000'0000'0000;
|
|
return bump_ptr;
|
|
}
|
|
template<bool committed>
|
|
void* reserve(size_t size) noexcept
|
|
{
|
|
DWORD flags = MEM_RESERVE;
|
|
|
|
if (committed)
|
|
flags |= MEM_COMMIT;
|
|
|
|
size_t retries = 1000;
|
|
void* p;
|
|
|
|
do
|
|
{
|
|
p = VirtualAlloc(
|
|
(void*)systematic_bump_ptr(), size, flags, PAGE_READWRITE);
|
|
|
|
systematic_bump_ptr() += size;
|
|
retries--;
|
|
} while (p == nullptr && retries > 0);
|
|
|
|
return p;
|
|
}
|
|
# elif defined(PLATFORM_HAS_VIRTUALALLOC2)
|
|
template<bool committed>
|
|
void* reserve(size_t size, size_t align) noexcept
|
|
{
|
|
DWORD flags = MEM_RESERVE;
|
|
|
|
if (committed)
|
|
flags |= MEM_COMMIT;
|
|
|
|
// Windows doesn't let you request memory less than 64KB aligned. Most
|
|
// operating systems will simply give you something more aligned than you
|
|
// ask for, but Windows complains about invalid parameters.
|
|
const size_t min_align = 64 * 1024;
|
|
if (align < min_align)
|
|
align = min_align;
|
|
|
|
// If we're on Windows 10 or newer, we can use the VirtualAlloc2
|
|
// function. The FromApp variant is useable by UWP applications and
|
|
// cannot allocate executable memory.
|
|
MEM_ADDRESS_REQUIREMENTS addressReqs = {NULL, NULL, align};
|
|
|
|
MEM_EXTENDED_PARAMETER param = {
|
|
{MemExtendedParameterAddressRequirements, 0}, {0}};
|
|
// Separate assignment as MSVC doesn't support .Pointer in the
|
|
// initialisation list.
|
|
param.Pointer = &addressReqs;
|
|
|
|
void* ret = VirtualAlloc2FromApp(
|
|
nullptr, nullptr, size, flags, PAGE_READWRITE, ¶m, 1);
|
|
if (ret == nullptr)
|
|
{
|
|
error("Failed to allocate memory\n");
|
|
}
|
|
return ret;
|
|
}
|
|
# else
|
|
template<bool committed>
|
|
void* reserve(size_t size) noexcept
|
|
{
|
|
DWORD flags = MEM_RESERVE;
|
|
|
|
if (committed)
|
|
flags |= MEM_COMMIT;
|
|
|
|
void* ret = VirtualAlloc(nullptr, size, flags, PAGE_READWRITE);
|
|
if (ret == nullptr)
|
|
{
|
|
error("Failed to allocate memory\n");
|
|
}
|
|
return ret;
|
|
}
|
|
# endif
|
|
};
|
|
}
|
|
#endif
|