//------------------------------------------------------------------------------------------------------- // Copyright (C) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. //------------------------------------------------------------------------------------------------------- #pragma once #include "PageAllocatorDefines.h" #include "Exceptions/ExceptionBase.h" #ifdef ENABLE_BASIC_TELEMETRY #include "AllocatorTelemetryStats.h" #endif #ifdef PROFILE_MEM struct PageMemoryData; #endif #if !FLOATVAR class CodeGenNumberThreadAllocator; struct XProcNumberPageSegmentManager; #endif namespace Memory { typedef void* FunctionTableHandle; #if DBG_DUMP #define GUARD_PAGE_TRACE(...) \ if (Js::Configuration::Global.flags.PrintGuardPageBounds) \ { \ Output::Print(__VA_ARGS__); \ } #define PAGE_ALLOC_TRACE(format, ...) PAGE_ALLOC_TRACE_EX(false, false, format, ##__VA_ARGS__) #define PAGE_ALLOC_VERBOSE_TRACE(format, ...) PAGE_ALLOC_TRACE_EX(true, false, format, ##__VA_ARGS__) #define PAGE_ALLOC_VERBOSE_TRACE_0(format) PAGE_ALLOC_TRACE_EX(true, false, format, "") #define PAGE_ALLOC_TRACE_AND_STATS(format, ...) PAGE_ALLOC_TRACE_EX(false, true, format, ##__VA_ARGS__) #define PAGE_ALLOC_TRACE_AND_STATS_0(format) PAGE_ALLOC_TRACE_EX(false, true, format, "") #define PAGE_ALLOC_VERBOSE_TRACE_AND_STATS(format, ...) PAGE_ALLOC_TRACE_EX(true, true, format, ##__VA_ARGS__) #define PAGE_ALLOC_VERBOSE_TRACE_AND_STATS_0(format) PAGE_ALLOC_TRACE_EX(true, true, format, "") #define PAGE_ALLOC_TRACE_EX(verbose, stats, format, ...) \ if (this->pageAllocatorFlagTable.Trace.IsEnabled(Js::PageAllocatorPhase)) \ { \ if (!verbose || this->pageAllocatorFlagTable.Verbose) \ { \ Output::Print(_u("%p : %p> PageAllocator(%p): "), GetCurrentThreadContextId(), ::GetCurrentThreadId(), this); \ if (debugName != nullptr) \ { \ Output::Print(_u("[%s] "), this->debugName); \ } \ Output::Print(format, ##__VA_ARGS__); \ Output::Print(_u("\n")); \ if (stats && this->pageAllocatorFlagTable.Stats.IsEnabled(Js::PageAllocatorPhase)) \ { \ this->DumpStats(); \ } \ Output::Flush(); \ } \ } #else #define PAGE_ALLOC_TRACE(format, ...) #define PAGE_ALLOC_VERBOSE_TRACE(format, ...) #define PAGE_ALLOC_VERBOSE_TRACE_0(format) #define PAGE_ALLOC_TRACE_AND_STATS(format, ...) #define PAGE_ALLOC_VERBOSE_TRACE_AND_STATS(format, ...) #define PAGE_ALLOC_TRACE_AND_STATS_0(format) #define PAGE_ALLOC_VERBOSE_TRACE_AND_STATS_0(format) #endif #ifdef TARGET_64 #define XDATA_RESERVE_PAGE_COUNT (2) // Number of pages per page segment (32 pages) reserved for xdata. #else #define XDATA_RESERVE_PAGE_COUNT (0) // ARM uses the heap, so it's not required. #endif // // Allocation done by the secondary allocator // struct SecondaryAllocation { BYTE* address; // address of the allocation by the secondary allocator SecondaryAllocation() : address(nullptr) { } }; #if defined(TARGET_64) struct XDataInfo { RUNTIME_FUNCTION pdata; FunctionTableHandle functionTable; }; #elif defined(_M_ARM) struct XDataInfo { ushort pdataCount; ushort xdataSize; FunctionTableHandle functionTable; }; #endif // // For every page segment a page allocator can create a secondary allocator which can have a specified // number of pages reserved for secondary allocations. These pages are always reserved at the end of the // segment. The PageAllocator itself cannot allocate from the region demarcated for the secondary allocator. // Currently this is used for xdata allocations. // class SecondaryAllocator { public: virtual bool Alloc(ULONG_PTR functionStart, DWORD functionSize, DECLSPEC_GUARD_OVERFLOW ushort pdataCount, DECLSPEC_GUARD_OVERFLOW ushort xdataSize, SecondaryAllocation* xdata) = 0; virtual void Release(const SecondaryAllocation& allocation) = 0; virtual void Delete() = 0; virtual bool CanAllocate() = 0; virtual ~SecondaryAllocator() {}; }; class PageAllocatorBaseCommon; class SegmentBaseCommon { // Disable create instance of PageAllocatorBaseCommon directly protected: SegmentBaseCommon(PageAllocatorBaseCommon* allocator); ~SegmentBaseCommon() {} protected: PageAllocatorBaseCommon* allocator; public: bool IsInPreReservedHeapPageAllocator() const; }; /* * A segment is a collection of pages. A page corresponds to the concept of an * OS memory page. Segments allocate memory using the OS VirtualAlloc call. * It'll allocate the pageCount * page size number of bytes, the latter being * a system-wide constant. */ template class SegmentBase: public SegmentBaseCommon { PREVENT_STANDALONE_HEAPINSTANCE(); public: SegmentBase(PageAllocatorBase * allocator, DECLSPEC_GUARD_OVERFLOW size_t pageCount, bool enableWriteBarrier); ~SegmentBase(); size_t GetPageCount() const { return segmentPageCount; } // Some pages are reserved upfront for secondary allocations // which are done by a secondary allocator as opposed to the PageAllocator size_t GetAvailablePageCount() const { return segmentPageCount - secondaryAllocPageCount; } char* GetSecondaryAllocStartAddress() const { return (this->address + GetAvailablePageCount() * AutoSystemInfo::PageSize); } uint GetSecondaryAllocSize() const { return this->secondaryAllocPageCount * AutoSystemInfo::PageSize; } uint GetSecondaryAllocPageCount() const { return this->secondaryAllocPageCount; } char* GetAddress() const { return address; } char* GetEndAddress() const { return GetSecondaryAllocStartAddress(); } bool CanAllocSecondary() { Assert(secondaryAllocator); return secondaryAllocator->CanAllocate(); } PageAllocatorBase* GetAllocator() const { return static_cast*>(allocator); } bool Initialize(DWORD allocFlags, bool excludeGuardPages); #if DBG bool IsPageSegment() const { return isPageSegment; } #endif bool IsInSegment(void* address) const { void* start = static_cast(GetAddress()); void* end = static_cast(GetEndAddress()); return (address >= start && address < end); } bool IsInCustomHeapAllocator() const { return this->GetAllocator()->type == PageAllocatorType::PageAllocatorType_CustomHeap; } SecondaryAllocator* GetSecondaryAllocator() { return secondaryAllocator; } #if defined(TARGET_64) && defined(RECYCLER_WRITE_BARRIER) bool IsWriteBarrierAllowed() { return isWriteBarrierAllowed; } bool IsWriteBarrierEnabled() { return this->isWriteBarrierEnabled; } #endif protected: #if TARGET_32 static const uint VirtualAllocThreshold = 524288; // 512kb As per spec #else // TARGET_64 static const uint VirtualAllocThreshold = 1048576; // 1MB As per spec : when we cross this threshold of bytes, we should add guard pages #endif static const uint maxGuardPages = 15; static const uint minGuardPages = 1; SecondaryAllocator* secondaryAllocator; char * address; size_t segmentPageCount; uint trailingGuardPageCount; uint leadingGuardPageCount; uint secondaryAllocPageCount; bool isWriteBarrierAllowed : 1; bool isWriteBarrierEnabled : 1; #if DBG bool isPageSegment : 1; #endif }; /* * Page Segments allows a client to deal with virtual memory on a page level * unlike Segment, which gives you access on a segment basis. Pages managed * by the page segment are initially in a "free list", and have the no access * bit set on them. When a client wants pages, we get them from the free list * and commit them into memory. When the client no longer needs those pages, * we simply decommit them- this means that the pages are still reserved for * the process but are not a part of its working set and has no physical * storage associated with it. */ template class PageSegmentBase : public SegmentBase { PREVENT_STANDALONE_HEAPINSTANCE(); typedef SegmentBase Base; public: PageSegmentBase(PageAllocatorBase * allocator, bool committed, bool allocated, bool enableWriteBarrier); PageSegmentBase(PageAllocatorBase * allocator, void* address, uint pageCount, uint committedCount, bool enableWriteBarrier); // Maximum possible size of a PageSegment; may be smaller. static const uint MaxDataPageCount = 256; // 1 MB static const uint MaxGuardPageCount = 16; static const uint MaxPageCount = MaxDataPageCount + MaxGuardPageCount; // 272 Pages typedef BVStatic PageBitVector; uint GetAvailablePageCount() const { size_t availablePageCount = Base::GetAvailablePageCount(); Assert(availablePageCount < MAXUINT32); return static_cast(availablePageCount); } void Prime(); #ifdef PAGEALLOCATOR_PROTECT_FREEPAGE bool Initialize(DWORD allocFlags, bool excludeGuardPages); #endif uint GetFreePageCount() const { return freePageCount; } uint GetDecommitPageCount() const { return decommitPageCount; } static bool IsAllocationPageAligned(__in char* address, size_t pageCount, uint *nextIndex = nullptr); template char * AllocDecommitPages(DECLSPEC_GUARD_OVERFLOW uint pageCount, T freePages, T decommitPages); template char * AllocPages(DECLSPEC_GUARD_OVERFLOW uint pageCount); void ReleasePages(__in void * address, uint pageCount); template void DecommitPages(__in void * address, uint pageCount); uint GetCountOfFreePages() const; uint GetNextBitInFreePagesBitVector(uint index) const; BOOLEAN TestRangeInFreePagesBitVector(uint index, uint pageCount) const; BOOLEAN TestInFreePagesBitVector(uint index) const; void ClearAllInFreePagesBitVector(); void ClearRangeInFreePagesBitVector(uint index, uint pageCount); void SetRangeInFreePagesBitVector(uint index, uint pageCount); void ClearBitInFreePagesBitVector(uint index); uint GetCountOfDecommitPages() const; BOOLEAN TestInDecommitPagesBitVector(uint index) const; BOOLEAN TestRangeInDecommitPagesBitVector(uint index, uint pageCount) const; void SetRangeInDecommitPagesBitVector(uint index, uint pageCount); void SetBitInDecommitPagesBitVector(uint index); void ClearRangeInDecommitPagesBitVector(uint index, uint pageCount); template char * DoAllocDecommitPages(DECLSPEC_GUARD_OVERFLOW uint pageCount); uint GetMaxPageCount(); size_t DecommitFreePages(size_t pageToDecommit); bool IsEmpty() const { return this->freePageCount == this->GetAvailablePageCount(); } // // If a segment has decommitted pages - then it's not considered full as allocations can take place from it // However, if secondary allocations cannot be made from it - it's considered full nonetheless // bool IsFull() const { return (this->freePageCount == 0 && !ShouldBeInDecommittedList()) || (this->secondaryAllocator != nullptr && !this->secondaryAllocator->CanAllocate()); } bool IsAllDecommitted() const { return this->GetAvailablePageCount() == this->decommitPageCount; } bool ShouldBeInDecommittedList() const { return this->decommitPageCount != 0; } bool IsFreeOrDecommitted(void* address, uint pageCount) const { Assert(this->IsInSegment(address)); uint base = GetBitRangeBase(address); return this->TestRangeInDecommitPagesBitVector(base, pageCount) || this->TestRangeInFreePagesBitVector(base, pageCount); } bool IsFreeOrDecommitted(void* address) const { Assert(this->IsInSegment(address)); uint base = GetBitRangeBase(address); return this->TestInDecommitPagesBitVector(base) || this->TestInFreePagesBitVector(base); } PageBitVector GetUnAllocatedPages() const { PageBitVector unallocPages = freePages; unallocPages.Or(&decommitPages); return unallocPages; } void ChangeSegmentProtection(DWORD protectFlags, DWORD expectedOldProtectFlags); //---------- Private members ---------------/ private: void DecommitFreePagesInternal(uint index, uint pageCount); uint GetBitRangeBase(void* address) const { uint base = ((uint)(((char *)address) - this->address)) / AutoSystemInfo::PageSize; return base; } PageBitVector freePages; PageBitVector decommitPages; uint freePageCount; uint decommitPageCount; }; template class HeapPageAllocator; /* * A Page Allocation is an allocation made by a page allocator * This has a base address, and tracks the number of pages that * were allocated from the segment */ class PageAllocation { public: char * GetAddress() const { return ((char *)this) + sizeof(PageAllocation); } size_t GetSize() const { return pageCount * AutoSystemInfo::PageSize - sizeof(PageAllocation); } size_t GetPageCount() const { return pageCount; } void* GetSegment() const { return segment; } private: size_t pageCount; void * segment; friend class PageAllocatorBase; #if ENABLE_NATIVE_CODEGEN friend class PageAllocatorBase; #endif #if ENABLE_OOP_NATIVE_CODEGEN friend class PageAllocatorBase; friend class PageAllocatorBase; #endif friend class HeapPageAllocator<>; }; class MemoryOperationLastError { public: static void RecordLastError() { #if ENABLE_OOP_NATIVE_CODEGEN if (MemOpLastError == S_OK) { MemOpLastError = HRESULT_FROM_WIN32(::GetLastError()); } #endif } static void RecordError(HRESULT error) { #if ENABLE_OOP_NATIVE_CODEGEN if (MemOpLastError == S_OK) { MemOpLastError = error; } #endif } static void ClearLastError() { #if ENABLE_OOP_NATIVE_CODEGEN MemOpLastError = S_OK; #endif } static HRESULT GetLastError() { #if ENABLE_OOP_NATIVE_CODEGEN return MemOpLastError; #else return S_OK; #endif } #if ENABLE_OOP_NATIVE_CODEGEN private: THREAD_LOCAL static HRESULT MemOpLastError; #endif }; class PageAllocatorBaseCommon { protected: // Disable create instance of PageAllocatorBaseCommon directly PageAllocatorBaseCommon() : virtualAllocator(nullptr), allocatorType(AllocatorType::VirtualAlloc) {} ~PageAllocatorBaseCommon() {} public: enum class AllocatorType { VirtualAlloc, #if ENABLE_NATIVE_CODEGEN PreReservedVirtualAlloc, #endif #if ENABLE_OOP_NATIVE_CODEGEN SectionAlloc, PreReservedSectionAlloc #endif }; template static AllocatorType GetAllocatorType(); AllocatorType GetAllocatorType() const { return this->allocatorType; } protected: void* virtualAllocator; AllocatorType allocatorType; }; template<> inline PageAllocatorBaseCommon::AllocatorType PageAllocatorBaseCommon::GetAllocatorType() { return AllocatorType::VirtualAlloc; }; #if ENABLE_NATIVE_CODEGEN template<> inline PageAllocatorBaseCommon::AllocatorType PageAllocatorBaseCommon::GetAllocatorType() { return AllocatorType::PreReservedVirtualAlloc; }; #endif #if ENABLE_OOP_NATIVE_CODEGEN template<> inline PageAllocatorBaseCommon::AllocatorType PageAllocatorBaseCommon::GetAllocatorType() { return AllocatorType::SectionAlloc; }; template<> inline PageAllocatorBaseCommon::AllocatorType PageAllocatorBaseCommon::GetAllocatorType() { return AllocatorType::PreReservedSectionAlloc; }; #endif /* * This allocator is responsible for allocating and freeing pages. It does * so by virtue of allocating segments for groups of pages, and then handing * out memory in these segments. It's also responsible for free segments. * This class also controls the idle decommit thread, which decommits pages * when they're no longer needed */ template class PageAllocatorBase: public PageAllocatorBaseCommon { #if !FLOATVAR friend class ::CodeGenNumberThreadAllocator; friend struct ::XProcNumberPageSegmentManager; #endif // Allowing recycler to report external memory allocation. friend class HeapInfo; public: static uint const DefaultMaxFreePageCount = 0x400; // 4 MB static uint const DefaultLowMaxFreePageCount = 0x100; // 1 MB for low-memory process static uint const MinPartialDecommitFreePageCount = 0x1000; // 16 MB static uint const DefaultMaxAllocPageCount = 32; // 128K static uint const DefaultSecondaryAllocPageCount = 0; static size_t GetProcessUsedBytes(); static size_t GetAndResetMaxUsedBytes(); // xplat TODO: implement a platform agnostic version of interlocked linked lists #if ENABLE_BACKGROUND_PAGE_FREEING struct FreePageEntry #if SUPPORT_WIN32_SLIST : public SLIST_ENTRY #endif { #if !SUPPORT_WIN32_SLIST FreePageEntry* Next; #endif PageSegmentBase * segment; uint pageCount; }; struct BackgroundPageQueue { #if SUPPORT_WIN32_SLIST SLIST_HEADER bgFreePageList; #else FreePageEntry* bgFreePageList; #endif CriticalSection backgroundPageQueueCriticalSection; #if DBG bool isZeroPageQueue; #endif BackgroundPageQueue() #if !SUPPORT_WIN32_SLIST :bgFreePageList(nullptr) #endif { #if SUPPORT_WIN32_SLIST ::InitializeSListHead(&bgFreePageList); #endif DebugOnly(this->isZeroPageQueue = false); } FreePageEntry* PopFreePageEntry() { #if SUPPORT_WIN32_SLIST return (FreePageEntry *)::InterlockedPopEntrySList(&bgFreePageList); #else AutoCriticalSection autoCS(&backgroundPageQueueCriticalSection); FreePageEntry* head = bgFreePageList; if (head) { bgFreePageList = bgFreePageList->Next; } return head; #endif } void PushFreePageEntry(FreePageEntry* entry) { #if SUPPORT_WIN32_SLIST ::InterlockedPushEntrySList(&bgFreePageList, entry); #else AutoCriticalSection autoCS(&backgroundPageQueueCriticalSection); entry->Next = bgFreePageList; bgFreePageList = entry; #endif } }; #if ENABLE_BACKGROUND_PAGE_ZEROING struct ZeroPageQueue : BackgroundPageQueue { #if SUPPORT_WIN32_SLIST SLIST_HEADER pendingZeroPageList; #else FreePageEntry* pendingZeroPageList; #endif ZeroPageQueue() #if !SUPPORT_WIN32_SLIST :BackgroundPageQueue(), pendingZeroPageList(nullptr) #endif { #if SUPPORT_WIN32_SLIST ::InitializeSListHead(&pendingZeroPageList); #endif DebugOnly(this->isZeroPageQueue = true); } FreePageEntry* PopZeroPageEntry() { #if SUPPORT_WIN32_SLIST return (FreePageEntry *)::InterlockedPopEntrySList(&pendingZeroPageList); #else AutoCriticalSection autoCS(&this->backgroundPageQueueCriticalSection); FreePageEntry* head = pendingZeroPageList; if (head) { pendingZeroPageList = pendingZeroPageList->Next; } return head; #endif } void PushZeroPageEntry(FreePageEntry* entry) { #if SUPPORT_WIN32_SLIST ::InterlockedPushEntrySList(&pendingZeroPageList, entry); #else AutoCriticalSection autoCS(&this->backgroundPageQueueCriticalSection); entry->Next = pendingZeroPageList; pendingZeroPageList = entry; #endif } USHORT QueryDepth() { #if SUPPORT_WIN32_SLIST return QueryDepthSList(&pendingZeroPageList); #else AutoCriticalSection autoCS(&this->backgroundPageQueueCriticalSection); FreePageEntry* head = pendingZeroPageList; size_t count = 0; while (head) { head = head->Next; count++; } // If the specified singly linked list contains more than 65535 entries, QueryDepthSList returns the number of entries in the list modulo 65535 return (USHORT)(count % 65536); #endif } }; #endif #endif PageAllocatorBase(AllocationPolicyManager * policyManager, Js::ConfigFlagsTable& flags = Js::Configuration::Global.flags, PageAllocatorType type = PageAllocatorType_Max, uint maxFreePageCount = DefaultMaxFreePageCount, bool zeroPages = false, #if ENABLE_BACKGROUND_PAGE_FREEING BackgroundPageQueue * backgroundPageQueue = nullptr, #endif uint maxAllocPageCount = DefaultMaxAllocPageCount, uint secondaryAllocPageCount = DefaultSecondaryAllocPageCount, bool stopAllocationOnOutOfMemory = false, bool excludeGuardPages = false, HANDLE processHandle = GetCurrentProcess(), bool enableWriteBarrier = false ); ~PageAllocatorBase(); bool IsClosed() const { return isClosed; } void Close() { Assert(!isClosed); isClosed = true; } AllocationPolicyManager * GetAllocationPolicyManager() const { return policyManager; } uint GetMaxAllocPageCount(); //VirtualAllocator APIs TVirtualAlloc * GetVirtualAllocator() const; PageAllocation * AllocPagesForBytes(DECLSPEC_GUARD_OVERFLOW size_t requestedBytes); PageAllocation * AllocAllocation(DECLSPEC_GUARD_OVERFLOW size_t pageCount); void ReleaseAllocation(PageAllocation * allocation); void ReleaseAllocationNoSuspend(PageAllocation * allocation); char * Alloc(size_t * pageCount, TSegment ** segment); void Release(void * address, size_t pageCount, void * segment); char * AllocPages(DECLSPEC_GUARD_OVERFLOW uint pageCount, TPageSegment ** pageSegment); char * AllocPagesPageAligned(DECLSPEC_GUARD_OVERFLOW uint pageCount, TPageSegment ** pageSegment); void ReleasePages(__in void * address, uint pageCount, __in void * pageSegment); #if ENABLE_BACKGROUND_PAGE_FREEING void BackgroundReleasePages(void * address, uint pageCount, TPageSegment * pageSegment); #endif void MemSetLocal(_In_ void *dst, int val, size_t sizeInBytes); // Decommit void DecommitNow(bool all = true); void SuspendIdleDecommit(); void ResumeIdleDecommit(); #if ENABLE_BACKGROUND_PAGE_ZEROING void StartQueueZeroPage(); void StopQueueZeroPage(); void ZeroQueuedPages(); void BackgroundZeroQueuedPages(); #endif #if ENABLE_BACKGROUND_PAGE_FREEING void FlushBackgroundPages(); #endif bool DisableAllocationOutOfMemory() const { return disableAllocationOutOfMemory; } void ResetDisableAllocationOutOfMemory() { disableAllocationOutOfMemory = false; } #ifdef RECYCLER_MEMORY_VERIFY void EnableVerify() { verifyEnabled = true; } #endif #if defined(RECYCLER_NO_PAGE_REUSE) || defined(ARENA_MEMORY_VERIFY) void ReenablePageReuse() { Assert(disablePageReuse); disablePageReuse = false; } bool DisablePageReuse() { bool wasDisablePageReuse = disablePageReuse; disablePageReuse = true; return wasDisablePageReuse; } bool IsPageReuseDisabled() { return disablePageReuse; } #endif #if DBG #if ENABLE_BACKGROUND_PAGE_ZEROING bool HasZeroQueuedPages() const; #endif virtual void SetDisableThreadAccessCheck() { disableThreadAccessCheck = true;} virtual void SetEnableThreadAccessCheck() { disableThreadAccessCheck = false; } virtual bool IsIdleDecommitPageAllocator() const { return false; } virtual bool HasMultiThreadAccess() const { return false; } bool ValidThreadAccess() { DWORD currentThreadId = ::GetCurrentThreadId(); return disableThreadAccessCheck || (this->concurrentThreadId == -1 && this->threadContextHandle == NULL) || // JIT thread after close (this->concurrentThreadId != -1 && this->concurrentThreadId == currentThreadId) || this->threadContextHandle == GetCurrentThreadContextId(); } virtual void UpdateThreadContextHandle(ThreadContextId updatedThreadContextHandle) { threadContextHandle = updatedThreadContextHandle; } void SetConcurrentThreadId(DWORD threadId) { this->concurrentThreadId = threadId; } void ClearConcurrentThreadId() { this->concurrentThreadId = (DWORD)-1; } DWORD GetConcurrentThreadId() { return this->concurrentThreadId; } DWORD HasConcurrentThreadId() { return this->concurrentThreadId != -1; } #endif bool IsWriteWatchEnabled() { return (allocFlags & MEM_WRITE_WATCH) == MEM_WRITE_WATCH; } #if DBG_DUMP char16 const * debugName; #endif #ifdef ENABLE_BASIC_TELEMETRY AllocatorDecommitStats* GetDecommitStats() { return this->decommitStats; } void SetDecommitStats(AllocatorDecommitStats* val) { this->decommitStats = val; } #endif protected: void InitVirtualAllocator(TVirtualAlloc * virtualAllocator); TSegment * AllocSegment(DECLSPEC_GUARD_OVERFLOW size_t pageCount); void ReleaseSegment(TSegment * segment); template char * AllocInternal(size_t * pageCount, TSegment ** segment); template char * SnailAllocPages(DECLSPEC_GUARD_OVERFLOW uint pageCount, TPageSegment ** pageSegment); void OnAllocFromNewSegment(DECLSPEC_GUARD_OVERFLOW uint pageCount, __in void* pages, TSegment* segment); template char * TryAllocFreePages(DECLSPEC_GUARD_OVERFLOW uint pageCount, TPageSegment ** pageSegment); #if ENABLE_BACKGROUND_PAGE_FREEING char * TryAllocFromZeroPagesList(DECLSPEC_GUARD_OVERFLOW uint pageCount, TPageSegment ** pageSegment, BackgroundPageQueue* bgPageQueue, bool isPendingZeroList); #endif char * TryAllocFromZeroPages(DECLSPEC_GUARD_OVERFLOW uint pageCount, TPageSegment ** pageSegment); template char * TryAllocDecommittedPages(DECLSPEC_GUARD_OVERFLOW uint pageCount, TPageSegment ** pageSegment); DListBase * GetSegmentList(TPageSegment * segment); void TransferSegment(TPageSegment * segment, DListBase * fromSegmentList); void FillAllocPages(__in void * address, uint pageCount); void FillFreePages(__in void * address, uint pageCount); bool IsPageSegment(TSegment* segment) { return segment->GetAvailablePageCount() <= maxAllocPageCount; } #if DBG_DUMP virtual void DumpStats() const; #endif TPageSegment * AddPageSegment(DListBase& segmentList); static TPageSegment * AllocPageSegment( DListBase& segmentList, PageAllocatorBase * pageAllocator, void* address, uint pageCount, uint committedCount, bool enableWriteBarrier); static TPageSegment * AllocPageSegment( DListBase& segmentList, PageAllocatorBase * pageAllocator, bool committed, bool allocated, bool enableWriteBarrier); // Zero Pages #if ENABLE_BACKGROUND_PAGE_ZEROING void AddPageToZeroQueue(__in void * address, uint pageCount, __in TPageSegment * pageSegment); bool HasZeroPageQueue() const; #endif bool ZeroPages() const { return zeroPages; } #if ENABLE_BACKGROUND_PAGE_ZEROING bool QueueZeroPages() const { return queueZeroPages; } #endif #if ENABLE_BACKGROUND_PAGE_FREEING FreePageEntry * PopPendingZeroPage(); #endif #if DBG void Check(); bool disableThreadAccessCheck; #endif protected: // Data DListBase segments; DListBase fullSegments; DListBase emptySegments; DListBase decommitSegments; DListBase largeSegments; uint maxAllocPageCount; DWORD allocFlags; uint maxFreePageCount; size_t freePageCount; uint secondaryAllocPageCount; bool isClosed; bool stopAllocationOnOutOfMemory; bool disableAllocationOutOfMemory; bool excludeGuardPages; bool enableWriteBarrier; AllocationPolicyManager * policyManager; Js::ConfigFlagsTable& pageAllocatorFlagTable; // zero pages bool zeroPages; #if ENABLE_BACKGROUND_PAGE_FREEING BackgroundPageQueue * backgroundPageQueue; #if ENABLE_BACKGROUND_PAGE_ZEROING bool queueZeroPages; bool hasZeroQueuedPages; #endif #endif // Idle Decommit bool isUsed; // A flag to indicate we are trying to enter IdleDecommit again and back-off from decommit in DecommitNow. This is to prevent // blocking UI thread for too long. We have seen hangs under AppVerifier and believe this may be due to the decommit being slower // under AppVerifier. This shouldn't be a problem otherwise. bool waitingToEnterIdleDecommit; #if DBG uint idleDecommitBackOffCount; #endif size_t minFreePageCount; uint idleDecommitEnterCount; void UpdateMinFreePageCount(); void ResetMinFreePageCount(); void ClearMinFreePageCount(); void AddFreePageCount(uint pageCount); static uint GetFreePageLimit() { return 0; } #if DBG size_t debugMinFreePageCount; ThreadContextId threadContextHandle; DWORD concurrentThreadId; #endif #if DBG_DUMP size_t decommitPageCount; #endif #ifdef RECYCLER_MEMORY_VERIFY bool verifyEnabled; #endif #if defined(RECYCLER_NO_PAGE_REUSE) || defined(ARENA_MEMORY_VERIFY) bool disablePageReuse; #endif friend TSegment; friend TPageSegment; friend class IdleDecommit; protected: virtual bool CreateSecondaryAllocator(TSegment* segment, bool committed, SecondaryAllocator** allocator) { *allocator = nullptr; return true; } bool IsAddressInSegment(__in void* address, const TPageSegment& segment); bool IsAddressInSegment(__in void* address, const TSegment& segment); HANDLE processHandle; private: uint GetSecondaryAllocPageCount() const { return this->secondaryAllocPageCount; } void IntegrateSegments(DListBase& segmentList, uint segmentCount, size_t pageCount); #if ENABLE_BACKGROUND_PAGE_FREEING void QueuePages(void * address, uint pageCount, TPageSegment * pageSegment); #endif template char* AllocPagesInternal(DECLSPEC_GUARD_OVERFLOW uint pageCount, TPageSegment ** pageSegment); #ifdef PROFILE_MEM PageMemoryData * memoryData; #endif #ifdef ENABLE_BASIC_TELEMETRY AllocatorDecommitStats* decommitStats; #endif size_t usedBytes; PageAllocatorType type; size_t reservedBytes; size_t committedBytes; size_t numberOfSegments; #ifdef PERF_COUNTERS PerfCounter::Counter& GetReservedSizeCounter() const { return PerfCounter::PageAllocatorCounterSet::GetReservedSizeCounter(type); } PerfCounter::Counter& GetCommittedSizeCounter() const { return PerfCounter::PageAllocatorCounterSet::GetCommittedSizeCounter(type); } PerfCounter::Counter& GetUsedSizeCounter() const { return PerfCounter::PageAllocatorCounterSet::GetUsedSizeCounter(type); } PerfCounter::Counter& GetTotalReservedSizeCounter() const { return PerfCounter::PageAllocatorCounterSet::GetTotalReservedSizeCounter(); } PerfCounter::Counter& GetTotalCommittedSizeCounter() const { return PerfCounter::PageAllocatorCounterSet::GetTotalCommittedSizeCounter(); } PerfCounter::Counter& GetTotalUsedSizeCounter() const { return PerfCounter::PageAllocatorCounterSet::GetTotalUsedSizeCounter(); } #endif void AddReservedBytes(size_t bytes); void SubReservedBytes(size_t bytes); void AddCommittedBytes(size_t bytes); void SubCommittedBytes(size_t bytes); void AddUsedBytes(size_t bytes); void SubUsedBytes(size_t bytes); void AddNumberOfSegments(size_t segmentCount); void SubNumberOfSegments(size_t segmentCount); public: size_t GetReservedBytes() const { return this->reservedBytes; }; size_t GetCommittedBytes() const { return this->committedBytes; } size_t GetUsedBytes() const { return this->usedBytes; } size_t GetNumberOfSegments() const { return this->numberOfSegments; } private: bool RequestAlloc(size_t byteCount) { if (disableAllocationOutOfMemory) { return false; } if (policyManager != nullptr) { return policyManager->RequestAlloc(byteCount); } return true; } void ReportExternalAlloc(size_t byteCount) { if (policyManager != nullptr) { policyManager->RequestAlloc(byteCount, true); } } void ReportFree(size_t byteCount) { if (policyManager != nullptr) { policyManager->ReportFree(byteCount); } } template void ReleaseSegmentList(DListBase * segmentList); protected: // Instrumentation void LogAllocSegment(TSegment * segment); void LogAllocSegment(uint segmentCount, size_t pageCount); void LogFreeSegment(TSegment * segment); void LogFreeDecommittedSegment(TSegment * segment); void LogFreePartiallyDecommittedPageSegment(TPageSegment * pageSegment); void LogAllocPages(size_t pageCount); void LogFreePages(size_t pageCount); void LogCommitPages(size_t pageCount); void LogRecommitPages(size_t pageCount); void LogDecommitPages(size_t pageCount); void ReportFailure(size_t byteCount) { if (this->stopAllocationOnOutOfMemory) { this->disableAllocationOutOfMemory = true; } if (policyManager != nullptr) { policyManager->ReportFailure(byteCount); } } }; template class HeapPageAllocator : public PageAllocatorBase { typedef PageAllocatorBase Base; PREVENT_STANDALONE_HEAPINSTANCE(); public: HeapPageAllocator(AllocationPolicyManager * policyManager, bool allocXdata, bool excludeGuardPages, TVirtualAlloc * virtualAllocator, HANDLE processHandle = nullptr); BOOL ProtectPages(__in char* address, size_t pageCount, __in void* segment, DWORD dwVirtualProtectFlags, DWORD desiredOldProtectFlag); bool AllocSecondary(void* segment, ULONG_PTR functionStart, DWORD functionSize, DECLSPEC_GUARD_OVERFLOW ushort pdataCount, DECLSPEC_GUARD_OVERFLOW ushort xdataSize, SecondaryAllocation* allocation); bool ReleaseSecondary(const SecondaryAllocation& allocation, void* segment); void TrackDecommittedPages(void * address, uint pageCount, __in void* segment); void DecommitPages(__in char* address, size_t pageCount = 1); // Release pages that has already been decommitted void ReleaseDecommitted(void * address, size_t pageCount, __in void * segment); bool IsAddressFromAllocator(__in void* address); bool AllocXdata() { return allocXdata; } private: bool allocXdata; void ReleaseDecommittedSegment(__in SegmentBase* segment); #if PDATA_ENABLED virtual bool CreateSecondaryAllocator(SegmentBase* segment, bool committed, SecondaryAllocator** allocator) override; #endif }; }