summaryrefslogtreecommitdiffstats
path: root/LibOVRKernel/Src/Kernel/OVR_Allocator.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'LibOVRKernel/Src/Kernel/OVR_Allocator.cpp')
-rw-r--r--LibOVRKernel/Src/Kernel/OVR_Allocator.cpp842
1 files changed, 842 insertions, 0 deletions
diff --git a/LibOVRKernel/Src/Kernel/OVR_Allocator.cpp b/LibOVRKernel/Src/Kernel/OVR_Allocator.cpp
new file mode 100644
index 0000000..c4f2ad9
--- /dev/null
+++ b/LibOVRKernel/Src/Kernel/OVR_Allocator.cpp
@@ -0,0 +1,842 @@
+/************************************************************************************
+
+Filename : OVR_Allocator.cpp
+Content : Installable memory allocator implementation
+Created : September 19, 2012
+Notes :
+
+Copyright : Copyright 2014 Oculus VR, LLC All Rights reserved.
+
+Licensed under the Oculus VR Rift SDK License Version 3.2 (the "License");
+you may not use the Oculus VR Rift SDK except in compliance with the License,
+which is provided at the time of installation or download, or which
+otherwise accompanies this software in either electronic or hard copy form.
+
+You may obtain a copy of the License at
+
+http://www.oculusvr.com/licenses/LICENSE-3.2
+
+Unless required by applicable law or agreed to in writing, the Oculus VR SDK
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+************************************************************************************/
+
+#include "OVR_Allocator.h"
+#include "OVR_DebugHelp.h"
+#include <stdlib.h>
+
+#ifdef OVR_OS_MAC
+ #include <stdlib.h>
+ #include <malloc/malloc.h>
+#else
+ #include <malloc.h>
+#endif
+
+#if defined(OVR_OS_MS)
+ #include "OVR_Win32_IncludeWindows.h"
+#elif defined(OVR_OS_MAC) || defined(OVR_OS_UNIX)
+ #include <unistd.h>
+ #include <sys/mman.h>
+#endif
+
+namespace OVR {
+
+
+//-----------------------------------------------------------------------------------
+// ***** Allocator
+
+Allocator* Allocator::pInstance = 0;
+
+// Default AlignedAlloc implementation will delegate to Alloc/Free after doing rounding.
+void* Allocator::AllocAligned(size_t size, size_t align)
+{
+ OVR_ASSERT((align & (align-1)) == 0);
+ align = (align > sizeof(size_t)) ? align : sizeof(size_t);
+ size_t p = (size_t)Alloc(size+align);
+ size_t aligned = 0;
+ if (p)
+ {
+ aligned = (size_t(p) + align-1) & ~(align-1);
+ if (aligned == p)
+ aligned += align;
+ *(((size_t*)aligned)-1) = aligned-p;
+ }
+
+ trackAlloc((void*)aligned, size);
+
+ return (void*)aligned;
+}
+
+void Allocator::FreeAligned(void* p)
+{
+ untrackAlloc((void*)p);
+
+ size_t src = size_t(p) - *(((size_t*)p) - 1);
+ Free((void*)src);
+}
+
+
+//------------------------------------------------------------------------
+// ***** Default Allocator
+
+// This allocator is created and used if no other allocator is installed.
+// Default allocator delegates to system malloc.
+
+void* DefaultAllocator::Alloc(size_t size)
+{
+ void* p = malloc(size);
+ trackAlloc(p, size);
+ return p;
+}
+void* DefaultAllocator::AllocDebug(size_t size, const char* file, unsigned line)
+{
+ OVR_UNUSED2(file, line); // should be here for debugopt config
+ void* p;
+#if defined(OVR_CC_MSVC) && defined(_CRTDBG_MAP_ALLOC)
+ p = _malloc_dbg(size, _NORMAL_BLOCK, file, line);
+#else
+ p = malloc(size);
+#endif
+ trackAlloc(p, size);
+ return p;
+}
+
+void* DefaultAllocator::Realloc(void* p, size_t newSize)
+{
+ void* newP = realloc(p, newSize);
+
+ // This used to more efficiently check if (newp != p) but static analyzers were erroneously flagging this.
+ if(newP) // Need to check newP because realloc doesn't free p unless it returns a valid newP.
+ {
+ #if !defined(__clang_analyzer__) // The analyzer complains that we are using p after it was freed.
+ untrackAlloc(p);
+ #endif
+ }
+ trackAlloc(newP, newSize);
+ return newP;
+}
+
+void DefaultAllocator::Free(void *p)
+{
+ untrackAlloc(p);
+ return free(p);
+}
+
+
+// System block allocator:
+
+void* SafeMMapAlloc(size_t size)
+{
+ #if defined(OVR_OS_MS)
+ return VirtualAlloc(nullptr, size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); // size is rounded up to a page. // Returned memory is 0-filled.
+
+ #elif defined(OVR_OS_MAC) || defined(OVR_OS_UNIX)
+ #if !defined(MAP_FAILED)
+ #define MAP_FAILED ((void*)-1)
+ #endif
+
+ void* result = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); // Returned memory is 0-filled.
+ if(result == MAP_FAILED) // mmap returns MAP_FAILED (-1) upon failure.
+ result = nullptr;
+ return result;
+ #endif
+}
+
+void SafeMMapFree(const void* memory, size_t size)
+{
+ #if defined(OVR_OS_MS)
+ OVR_UNUSED(size);
+ VirtualFree(const_cast<void*>(memory), 0, MEM_RELEASE);
+
+ #elif defined(OVR_OS_MAC) || defined(OVR_OS_UNIX)
+ size_t pageSize = getpagesize();
+ size = (((size + (pageSize - 1)) / pageSize) * pageSize);
+ munmap(const_cast<void*>(memory), size); // Must supply the size to munmap.
+ #endif
+}
+
+
+//------------------------------------------------------------------------
+// ***** Track Allocations
+
+struct TrackedAlloc
+{
+ TrackedAlloc* pNext;
+ TrackedAlloc* pPrev;
+ void* pAlloc;
+ void* Callstack[64];
+ uint32_t FrameCount;
+ uint32_t Size;
+};
+
+static TrackedAlloc* TrackHead = nullptr;
+static SymbolLookup Symbols;
+static bool IsLeakTracking = false;
+
+void Allocator::SetLeakTracking(bool enabled)
+{
+#if defined(OVR_OS_WIN32) && !defined(OVR_OS_WIN64)
+ // HACK: Currently 32-bit leak tracing is too slow to run in real-time on Windows.
+ // Note: We can possibly fix this by making a custom Win32 backtrace function which
+ // takes advantage of the fact that we have enabled stack frames in all builds.
+ enabled = false;
+#endif
+
+ IsLeakTracking = enabled;
+}
+
+bool Allocator::IsTrackingLeaks()
+{
+ return IsLeakTracking;
+}
+
+void Allocator::trackAlloc(void* p, size_t size)
+{
+ if (!p || !IsLeakTracking)
+ return;
+
+ Lock::Locker locker(&TrackLock);
+
+ TrackedAlloc* tracked = (TrackedAlloc*)malloc(sizeof(TrackedAlloc));
+ if (tracked)
+ {
+ memset(tracked, 0, sizeof(TrackedAlloc));
+
+ tracked->pAlloc = p;
+ tracked->pPrev = nullptr;
+ tracked->FrameCount = (uint32_t)Symbols.GetBacktrace(tracked->Callstack, OVR_ARRAY_COUNT(tracked->Callstack), 2);
+ tracked->Size = (uint32_t)size;
+
+ tracked->pNext = TrackHead;
+ if (TrackHead)
+ {
+ TrackHead->pPrev = tracked;
+ }
+ TrackHead = tracked;
+ }
+}
+
+void Allocator::untrackAlloc(void* p)
+{
+ if (!p || !IsLeakTracking)
+ return;
+
+ Lock::Locker locker(&TrackLock);
+
+ for (TrackedAlloc* t = TrackHead; t; t = t->pNext)
+ {
+ if (t->pAlloc == p)
+ {
+ if (t->pPrev)
+ {
+ t->pPrev->pNext = t->pNext;
+ }
+ if (t->pNext)
+ {
+ t->pNext->pPrev = t->pPrev;
+ }
+ if (TrackHead == t)
+ {
+ TrackHead = t->pNext;
+ }
+ free(t);
+
+ break;
+ }
+ }
+}
+
+int DumpMemory()
+{
+ const bool symbolLookupWasInitialized = SymbolLookup::IsInitialized();
+ const bool symbolLookupAvailable = SymbolLookup::Initialize();
+
+ if(!symbolLookupWasInitialized) // If SymbolLookup::Initialize was the first time being initialized, we need to refresh the Symbols view of modules, etc.
+ Symbols.Refresh();
+
+ // If we're dumping while LibOVR is running, then we should hold the lock.
+ Allocator* pAlloc = Allocator::GetInstance();
+
+ // It's possible this is being called after the Allocator was shut down, at which
+ // point we assume we are the only instance that can be executing at his time.
+ Lock* lock = pAlloc ? &pAlloc->TrackLock : nullptr;
+ if (lock)
+ lock->DoLock();
+
+ int leakCount = 0;
+
+ for (TrackedAlloc* t = TrackHead; t; t = t->pNext)
+ {
+ LogError("[Leak] ** Detected leaked allocation at %p (size = %u) (%d frames)", t->pAlloc, (unsigned)t->Size, (unsigned)t->FrameCount);
+
+ for (size_t i = 0; i < t->FrameCount; ++i)
+ {
+ SymbolInfo symbolInfo;
+
+ if (symbolLookupAvailable && Symbols.LookupSymbol((uint64_t)t->Callstack[i], symbolInfo) && (symbolInfo.filePath[0] || symbolInfo.function[0]))
+ {
+ if(symbolInfo.filePath[0])
+ LogText("%s(%d): %s\n", symbolInfo.filePath, symbolInfo.fileLineNumber, symbolInfo.function[0] ? symbolInfo.function : "(unknown function)");
+ else
+ LogText("%p (unknown source file): %s\n", t->Callstack[i], symbolInfo.function);
+ }
+ else
+ {
+ LogText("%p (symbols unavailable)\n", t->Callstack[i]);
+ }
+ }
+
+ ++leakCount;
+ }
+
+ if (lock)
+ lock->Unlock();
+
+ if(symbolLookupAvailable)
+ SymbolLookup::Shutdown();
+
+ return leakCount;
+}
+
+
+
+//------------------------------------------------------------------------
+// ***** DebugPageAllocator
+
+static size_t AlignSizeUp(size_t value, size_t alignment)
+{
+ return ((value + (alignment - 1)) & ~(alignment - 1));
+}
+
+static size_t AlignSizeDown(size_t value, size_t alignment)
+{
+ return (value & ~(alignment - 1));
+}
+
+template <typename Pointer>
+Pointer AlignPointerUp(Pointer p, size_t alignment)
+{
+ return reinterpret_cast<Pointer>(((reinterpret_cast<size_t>(p) + (alignment - 1)) & ~(alignment - 1)));
+}
+
+template <typename Pointer>
+Pointer AlignPointerDown(Pointer p, size_t alignment)
+{
+ return reinterpret_cast<Pointer>(reinterpret_cast<size_t>(p) & ~(alignment-1));
+}
+
+
+const size_t kFreedBlockArrayMaxSizeDefault = 16384;
+
+
+DebugPageAllocator::DebugPageAllocator()
+ : FreedBlockArray(nullptr)
+ , FreedBlockArrayMaxSize(0)
+ , FreedBlockArraySize(0)
+ , FreedBlockArrayOldest(0)
+ , AllocationCount(0)
+ , OverrunPageEnabled(true)
+ #if defined(OVR_BUILD_DEBUG)
+ , OverrunGuardBytesEnabled(true)
+ #else
+ , OverrunGuardBytesEnabled(false)
+ #endif
+ //PageSize(0)
+ , Lock()
+{
+ #if defined(_WIN32)
+ SYSTEM_INFO systemInfo;
+ GetSystemInfo(&systemInfo);
+ PageSize = (size_t)systemInfo.dwPageSize;
+ #else
+ PageSize = 4096;
+ #endif
+
+ SetDelayedFreeCount(kFreedBlockArrayMaxSizeDefault);
+}
+
+
+DebugPageAllocator::~DebugPageAllocator()
+{
+ Shutdown();
+}
+
+
+void DebugPageAllocator::Init()
+{
+ // Nothing to do.
+}
+
+void DebugPageAllocator::Shutdown()
+{
+ Lock::Locker autoLock(&Lock);
+
+ for(size_t i = 0; i < FreedBlockArraySize; i++)
+ {
+ if(FreedBlockArray[i].BlockPtr)
+ {
+ FreePageMemory(FreedBlockArray[i].BlockPtr, FreedBlockArray[i].BlockSize);
+ FreedBlockArray[i].Clear();
+ }
+ }
+
+ SetDelayedFreeCount(0);
+ FreedBlockArraySize = 0;
+ FreedBlockArrayOldest = 0;
+}
+
+
+void DebugPageAllocator::EnableOverrunDetection(bool enableOverrunDetection, bool enableOverrunGuardBytes)
+{
+ OVR_ASSERT(AllocationCount == 0);
+
+ OverrunPageEnabled = enableOverrunDetection;
+ OverrunGuardBytesEnabled = (enableOverrunDetection && enableOverrunGuardBytes); // Set OverrunGuardBytesEnabled to false if enableOverrunDetection is false.
+}
+
+
+void DebugPageAllocator::SetDelayedFreeCount(size_t delayedFreeCount)
+{
+ OVR_ASSERT(AllocationCount == 0);
+
+ if(FreedBlockArray)
+ {
+ SafeMMapFree(FreedBlockArray, FreedBlockArrayMaxSize * sizeof(Block));
+ FreedBlockArrayMaxSize = 0;
+ }
+
+ if(delayedFreeCount)
+ {
+ FreedBlockArray = (Block*)SafeMMapAlloc(delayedFreeCount * sizeof(Block));
+ OVR_ASSERT(FreedBlockArray);
+
+ if(FreedBlockArray)
+ {
+ FreedBlockArrayMaxSize = delayedFreeCount;
+ #if defined(OVR_BUILD_DEBUG)
+ memset(FreedBlockArray, 0, delayedFreeCount * sizeof(Block));
+ #endif
+ }
+ }
+}
+
+
+size_t DebugPageAllocator::GetDelayedFreeCount() const
+{
+ return FreedBlockArrayMaxSize;
+}
+
+
+void* DebugPageAllocator::Alloc(size_t size)
+{
+ #if defined(_WIN32)
+ return AllocAligned(size, DefaultAlignment);
+ #else
+ void* p = malloc(size);
+ trackAlloc(p, size);
+ return p;
+ #endif
+}
+
+
+void* DebugPageAllocator::AllocAligned(size_t size, size_t align)
+{
+ #if defined(_WIN32)
+ OVR_ASSERT(align <= PageSize);
+
+ Lock::Locker autoLock(&Lock);
+
+ if(align < DefaultAlignment)
+ align = DefaultAlignment;
+
+ // The actual needed size may be a little less than this, but it's hard to tell how the size and alignments will play out.
+ size_t maxRequiredSize = AlignSizeUp(size, align) + SizeStorageSize;
+
+ if(align > SizeStorageSize)
+ {
+ // Must do: more sophisticated fitting, as maxRequiredSize is potentially too small.
+ OVR_ASSERT(SizeStorageSize <= align);
+ }
+
+ size_t blockSize = AlignSizeUp(maxRequiredSize, PageSize);
+
+ if(OverrunPageEnabled)
+ blockSize += PageSize; // We add another page which will be uncommitted, so any read or write with it will except.
+
+ void* pBlockPtr;
+
+ if((FreedBlockArraySize == FreedBlockArrayMaxSize) && FreedBlockArrayMaxSize && // If there is an old block we can recycle...
+ (FreedBlockArray[FreedBlockArrayOldest].BlockSize == blockSize)) // We require it to be the exact size, as there would be some headaches for us if it was over-sized.
+ {
+ pBlockPtr = EnablePageMemory(FreedBlockArray[FreedBlockArrayOldest].BlockPtr, blockSize); // Convert this memory from PAGE_NOACCESS back to PAGE_READWRITE.
+ FreedBlockArray[FreedBlockArrayOldest].Clear();
+
+ if(++FreedBlockArrayOldest == FreedBlockArrayMaxSize)
+ FreedBlockArrayOldest = 0;
+ }
+ else
+ {
+ pBlockPtr = AllocCommittedPageMemory(blockSize); // Allocate a new block of one or more pages (via VirtualAlloc).
+ }
+
+ if(pBlockPtr)
+ {
+ void* pUserPtr = GetUserPosition(pBlockPtr, blockSize, size, align);
+ size_t* pSizePos = GetSizePosition(pUserPtr);
+
+ pSizePos[UserSizeIndex] = size;
+ pSizePos[BlockSizeIndex] = blockSize;
+ AllocationCount++;
+ trackAlloc(pUserPtr, size);
+
+ return pUserPtr;
+ }
+
+ return nullptr;
+ #else
+ OVR_ASSERT_AND_UNUSED(align <= DefaultAlignment, align);
+ return DebugPageAllocator::Alloc(size);
+ #endif
+}
+
+
+size_t DebugPageAllocator::GetUserSize(const void* p)
+{
+ #if defined(_WIN32)
+ return GetSizePosition(p)[UserSizeIndex];
+ #elif defined(__APPLE__)
+ return malloc_size(p);
+ #else
+ return malloc_usable_size(const_cast<void*>(p));
+ #endif
+}
+
+
+size_t DebugPageAllocator::GetBlockSize(const void* p)
+{
+ #if defined(_WIN32)
+ return GetSizePosition(p)[BlockSizeIndex];
+ #else
+ OVR_UNUSED(p);
+ return 0;
+ #endif
+}
+
+
+size_t* DebugPageAllocator::GetSizePosition(const void* p)
+{
+ // No thread safety required as per our design, as we assume that anybody
+ // who owns a pointer returned by Alloc cannot have another thread take it away.
+
+ // We assume the pointer is a valid pointer allocated by this allocator.
+ // We store some size values into the memory returned to the user, a few bytes before it.
+ size_t value = reinterpret_cast<size_t>(p);
+ size_t valuePos = (value - SizeStorageSize);
+ size_t* pSize = reinterpret_cast<size_t*>(valuePos);
+
+ return pSize;
+}
+
+
+void* DebugPageAllocator::Realloc(void* p, size_t newSize)
+{
+ #if defined(_WIN32)
+ return ReallocAligned(p, newSize, DefaultAlignment);
+ #else
+ void* newP = realloc(p, newSize);
+
+ if(newP) // Need to check newP because realloc doesn't free p unless it returns a valid newP.
+ {
+ #if !defined(__clang_analyzer__) // The analyzer complains that we are using p after it was freed.
+ untrackAlloc(p);
+ #endif
+ }
+ trackAlloc(newP, newSize);
+ return newP;
+ #endif
+}
+
+
+void* DebugPageAllocator::ReallocAligned(void* p, size_t newSize, size_t newAlign)
+{
+ #if defined(_WIN32)
+ // The ISO C99 standard states:
+ // The realloc function deallocates the old object pointed to by ptr and
+ // returns a pointer to a new object that has the size specified by size.
+ // The contents of the new object shall be the same as that of the old
+ // object prior to deallocation, up to the lesser of the new and old sizes.
+ // Any bytes in the new object beyond the size of the old object have
+ // indeterminate values.
+ //
+ // If ptr is a null pointer, the realloc function behaves like the malloc
+ // function for the specified size. Otherwise, if ptr does not match a
+ // pointer earlier returned by the calloc, malloc, or realloc function,
+ // or if the space has been deallocated by a call to the free or realloc
+ // function, the behavior is undefined. If memory for the new object
+ // cannot be allocated, the old object is not deallocated and its value
+ // is unchanged.
+ //
+ // The realloc function returns a pointer to the new object (which may have
+ // the same value as a pointer to the old object), or a null pointer if
+ // the new object could not be allocated.
+
+ // A mutex lock isn't required, as the functions below will handle it internally.
+ // But having it here is a little more efficient because it woudl otherwise be
+ // locked and unlocked multiple times below, with possible context switches in between.
+ Lock::Locker autoLock(&Lock);
+
+ void* pReturn = nullptr;
+
+ if(p)
+ {
+ if(newSize)
+ {
+ pReturn = AllocAligned(newSize, newAlign);
+
+ if(pReturn)
+ {
+ size_t prevSize = GetUserSize(p);
+
+ if(newSize > prevSize)
+ newSize = prevSize;
+
+ memcpy(pReturn, p, newSize);
+ Free(p);
+ } // Else fall through, leaving p's memory unmodified and returning nullptr.
+ }
+ else
+ {
+ Free(p);
+ }
+ }
+ else if(newSize)
+ {
+ pReturn = AllocAligned(newSize, newAlign);
+ }
+
+ return pReturn;
+ #else
+ OVR_ASSERT_AND_UNUSED(newAlign <= DefaultAlignment, newAlign);
+ return DebugPageAllocator::Realloc(p, newSize);
+ #endif
+}
+
+
+void DebugPageAllocator::Free(void *p)
+{
+ #if defined(_WIN32)
+ if(p)
+ {
+ Lock::Locker autoLock(&Lock);
+
+ if(FreedBlockArrayMaxSize) // If we have a delayed free list...
+ {
+ // We don't free the page(s) associated with this but rather put them in the FreedBlockArray in an inaccessible state for later freeing.
+ // We do this because we don't want those pages to be available again in the near future, so we can detect use-after-free misakes.
+ Block* pBlockNew;
+
+ if(FreedBlockArraySize == FreedBlockArrayMaxSize) // If we have reached freed block capacity... we can start purging old elements from it as a circular queue.
+ {
+ pBlockNew = &FreedBlockArray[FreedBlockArrayOldest];
+
+ // The oldest element in the container is FreedBlockArrayOldest.
+ if(pBlockNew->BlockPtr) // Currently this should always be true.
+ {
+ FreePageMemory(pBlockNew->BlockPtr, pBlockNew->BlockSize);
+ pBlockNew->Clear();
+ }
+
+ if(++FreedBlockArrayOldest == FreedBlockArrayMaxSize)
+ FreedBlockArrayOldest = 0;
+ }
+ else // Else we are still building the container and not yet treating it a circular.
+ {
+ pBlockNew = &FreedBlockArray[FreedBlockArraySize++];
+ }
+
+ pBlockNew->BlockPtr = GetBlockPtr(p);
+ pBlockNew->BlockSize = GetBlockSize(p);
+
+ #if defined(OVR_BUILD_DEBUG)
+ if(OverrunGuardBytesEnabled) // If we have extra bytes at the end of the user's allocation between it and an inaccessible guard page...
+ {
+ const size_t userSize = GetUserSize(p);
+ const uint8_t* pUserEnd = (static_cast<uint8_t*>(p) + userSize);
+ const uint8_t* pPageEnd = AlignPointerUp(pUserEnd, PageSize);
+
+ while(pUserEnd != pPageEnd)
+ {
+ if(*pUserEnd++ != GuardFillByte)
+ {
+ OVR_FAIL();
+ break;
+ }
+ }
+ }
+ #endif
+
+ DisablePageMemory(pBlockNew->BlockPtr, pBlockNew->BlockSize); // Make it so that future attempts to use this memory result in an exception.
+ }
+ else
+ {
+ FreePageMemory(GetBlockPtr(p), GetBlockSize(p));
+ }
+
+ untrackAlloc(p);
+ AllocationCount--;
+ }
+ #else
+ untrackAlloc(p);
+ return free(p);
+ #endif
+}
+
+
+void DebugPageAllocator::FreeAligned(void* p)
+{
+ return Free(p);
+}
+
+
+// Converts a user pointer to the beginning of its page.
+void* DebugPageAllocator::GetBlockPtr(void* p)
+{
+ // We store size info before p in memory, and this will, by design, be always somewhere within
+ // the first page of a block of pages. So just align down to the beginning of its page.
+ return AlignPointerDown(GetSizePosition(p), PageSize);
+}
+
+
+void* DebugPageAllocator::GetUserPosition(void* pPageMemory, size_t blockSize, size_t userSize, size_t userAlignment)
+{
+ uint8_t* pUserPosition;
+
+ if(OverrunPageEnabled)
+ {
+ // We need to return the highest position within the page memory that fits the user size while being aligned to userAlignment.
+ const size_t pageEnd = reinterpret_cast<size_t>(pPageMemory) + (blockSize - PageSize); // pageEnd points to the beginning of the final guard page.
+ const size_t userPosition = AlignSizeDown(pageEnd - userSize, userAlignment);
+ pUserPosition = reinterpret_cast<uint8_t*>(userPosition);
+ OVR_ASSERT((userPosition + userSize) <= pageEnd);
+
+ // If userSize is not a multiple of userAlignment then there will be (userAlignment - userSize) bytes
+ // of unused memory between the user allocated space and the end of the page. There is no way around having this.
+ // For example, a user allocation of 3 bytes with 8 byte alignment will leave 5 unused bytes at the end of the page.
+ // We optionally fill those unused bytes with a pattern and upon Free verify that the pattern is undisturbed.
+ // This won't detect reads or writes in that area immediately as with reads or writes beyond that, but it will
+ // at least detect them at some point (e.g. upon Free).
+ #if defined(OVR_BUILD_DEBUG)
+ if(OverrunGuardBytesEnabled)
+ {
+ uint8_t* const pUserEnd = (pUserPosition + userSize);
+ const size_t remainingByteCount = (reinterpret_cast<uint8_t*>(pageEnd) - pUserEnd);
+ if(remainingByteCount) // If there are any left-over bytes...
+ memset(pUserEnd, GuardFillByte, remainingByteCount);
+ }
+ #endif
+ }
+ else
+ {
+ // We need to return the first position in the first page after SizeStorageSize bytes which is aligned to userAlignment.
+ const size_t lowestPossiblePos = reinterpret_cast<size_t>(pPageMemory) + SizeStorageSize;
+ const size_t userPosition = AlignSizeUp(lowestPossiblePos, userAlignment);
+ pUserPosition = reinterpret_cast<uint8_t*>(userPosition);
+ OVR_ASSERT((userPosition + userSize) <= (reinterpret_cast<size_t>(pPageMemory) + blockSize));
+ }
+
+ // Assert that the returned user pointer (actually the size info before it) will be within the first page.
+ // This is important because it verifieds that we haven't wasted memory and because
+ // our functionality for telling the start of the page block depends on it.
+ OVR_ASSERT(AlignPointerDown(GetSizePosition(pUserPosition), PageSize) == pPageMemory);
+
+ return pUserPosition;
+}
+
+
+void* DebugPageAllocator::AllocCommittedPageMemory(size_t blockSize)
+{
+ #if defined(_WIN32)
+ void* p;
+
+ if(OverrunPageEnabled)
+ {
+ // We need to make it so that last page is MEM_RESERVE and the previous pages are MEM_COMMIT + PAGE_READWRITE.
+ OVR_ASSERT(blockSize > PageSize); // There should always be at least one extra page.
+
+ // Reserve blockSize amount of pages.
+ // We could possibly use PAGE_GUARD here for the last page. This differs from simply leaving it reserved
+ // because the OS will generate a one-time-only gaurd page exception. We probabl don't want this, as it's
+ // more useful for maintaining your own stack than for catching unintended overruns.
+ p = VirtualAlloc(nullptr, blockSize, MEM_RESERVE, PAGE_READWRITE);
+ OVR_ASSERT(p);
+
+ if(p)
+ {
+ // Commit all but the last page. Leave the last page as merely reserved so that reads from or writes
+ // to it result in an immediate exception.
+ p = VirtualAlloc(p, blockSize - PageSize, MEM_COMMIT, PAGE_READWRITE);
+ OVR_ASSERT(p);
+ }
+ }
+ else
+ {
+ // We need to make it so that all pages are MEM_COMMIT + PAGE_READWRITE.
+
+ p = VirtualAlloc(nullptr, blockSize, MEM_COMMIT, PAGE_READWRITE);
+ }
+
+ return p;
+ #else
+ OVR_UNUSED2(blockSize, OverrunPageEnabled);
+ return nullptr;
+ #endif
+}
+
+
+// We convert disabled page memory (see DisablePageMemory) to enabled page memory. The output is the same
+// as with AllocPageMemory.
+void* DebugPageAllocator::EnablePageMemory(void* pPageMemory, size_t blockSize)
+{
+ #if defined(_WIN32)
+ // Make sure the entire range of memory is of type PAGE_READWRITE.
+ DWORD dwPrevAccess = 0;
+ BOOL result = VirtualProtect(pPageMemory, OverrunPageEnabled ? (blockSize - PageSize) : blockSize, PAGE_READWRITE, &dwPrevAccess);
+ OVR_ASSERT_AND_UNUSED(result, result);
+ #else
+ OVR_UNUSED3(pPageMemory, blockSize, OverrunPageEnabled);
+ #endif
+
+ return pPageMemory;
+}
+
+
+void DebugPageAllocator::DisablePageMemory(void* pPageMemory, size_t blockSize)
+{
+ #if defined(_WIN32)
+ // Disable access to the page(s). It's faster for us to change the page access than it is to decommit or free the pages.
+ // However, this results in more committed physical memory usage than we would need if we instead decommitted the memory.
+ DWORD dwPrevAccesss = 0;
+ BOOL result = VirtualProtect(pPageMemory, OverrunPageEnabled ? (blockSize - PageSize) : blockSize, PAGE_NOACCESS, &dwPrevAccesss);
+ OVR_ASSERT_AND_UNUSED(result, result);
+ #else
+ OVR_UNUSED2(pPageMemory, blockSize);
+ #endif
+}
+
+
+void DebugPageAllocator::FreePageMemory(void* pPageMemory, size_t /*blockSize*/)
+{
+ #if defined(_WIN32)
+ BOOL result = VirtualFree(pPageMemory, 0, MEM_RELEASE);
+ OVR_ASSERT_AND_UNUSED(result, result);
+ #else
+ OVR_UNUSED(pPageMemory);
+ #endif
+}
+
+
+
+} // namespace OVR