summaryrefslogtreecommitdiff
path: root/src/gc
diff options
context:
space:
mode:
authorSean Gillespie <sean@swgillespie.me>2016-11-16 09:34:57 -0800
committerGitHub <noreply@github.com>2016-11-16 09:34:57 -0800
commitbb9f30e4658342f83bb3b5639ae24ec92e0a1d2a (patch)
treee2d7e09a74aac2bfead1315fc64e2515ba057145 /src/gc
parent2c0a2c05ba82460a8d8a4b1e2d98e908e59d5d54 (diff)
downloadcoreclr-bb9f30e4658342f83bb3b5639ae24ec92e0a1d2a.tar.gz
coreclr-bb9f30e4658342f83bb3b5639ae24ec92e0a1d2a.tar.bz2
coreclr-bb9f30e4658342f83bb3b5639ae24ec92e0a1d2a.zip
Enable GCToOSInterface to be defined on the GC side of the GC interface (#8121)
* Re-introduce changes lost in a merge conflict * Enable GCToOSInterface to be defined behind the GC interface when building the GC in standalone mode. Provide a skeleton Windows implementation and the framework for a Unix implementation. * Address code review feedback
Diffstat (limited to 'src/gc')
-rw-r--r--src/gc/CMakeLists.txt12
-rw-r--r--src/gc/env/gcenv.os.h3
-rw-r--r--src/gc/gc.cpp10
-rw-r--r--src/gc/gc.h12
-rw-r--r--src/gc/gcenv.unix.cpp310
-rw-r--r--src/gc/gcenv.windows.cpp669
-rw-r--r--src/gc/gcpriv.h2
-rw-r--r--src/gc/handletablecache.cpp6
-rw-r--r--src/gc/handletablecore.cpp2
-rw-r--r--src/gc/sample/gcenv.windows.cpp2
-rw-r--r--src/gc/softwarewritewatch.cpp1
11 files changed, 1020 insertions, 9 deletions
diff --git a/src/gc/CMakeLists.txt b/src/gc/CMakeLists.txt
index 61e1ced727..d32d1c2dfb 100644
--- a/src/gc/CMakeLists.txt
+++ b/src/gc/CMakeLists.txt
@@ -38,6 +38,18 @@ set( GC_SOURCES_WKS
set( GC_SOURCES_DAC
${GC_SOURCES_DAC_AND_WKS_COMMON})
+if(FEATURE_STANDALONE_GC)
+ if(CLR_CMAKE_PLATFORM_UNIX)
+ set ( GC_SOURCES_WKS
+ ${GC_SOURCES_WKS}
+ gcenv.unix.cpp)
+ else()
+ set ( GC_SOURCES_WKS
+ ${GC_SOURCES_WKS}
+ gcenv.windows.cpp)
+ endif(CLR_CMAKE_PLATFORM_UNIX)
+endif(FEATURE_STANDALONE_GC)
+
convert_to_absolute_path(GC_SOURCES_WKS ${GC_SOURCES_WKS})
convert_to_absolute_path(GC_SOURCES_DAC ${GC_SOURCES_DAC})
diff --git a/src/gc/env/gcenv.os.h b/src/gc/env/gcenv.os.h
index bb0153f117..6a126f29ed 100644
--- a/src/gc/env/gcenv.os.h
+++ b/src/gc/env/gcenv.os.h
@@ -73,13 +73,12 @@ public:
// Reserve virtual memory range.
// Parameters:
- // address - starting virtual address, it can be NULL to let the function choose the starting address
// size - size of the virtual memory range
// alignment - requested memory alignment
// flags - flags to control special settings like write watching
// Return:
// Starting virtual address of the reserved range
- static void* VirtualReserve(void *address, size_t size, size_t alignment, uint32_t flags);
+ static void* VirtualReserve(size_t size, size_t alignment, uint32_t flags);
// Release virtual memory range previously reserved using VirtualReserve
// Parameters:
diff --git a/src/gc/gc.cpp b/src/gc/gc.cpp
index b8c7b7895f..28e19aa04d 100644
--- a/src/gc/gc.cpp
+++ b/src/gc/gc.cpp
@@ -4288,7 +4288,7 @@ void* virtual_alloc (size_t size)
flags = VirtualReserveFlags::WriteWatch;
}
#endif // !FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP
- void* prgmem = GCToOSInterface::VirtualReserve (0, requested_size, card_size * card_word_width, flags);
+ void* prgmem = GCToOSInterface::VirtualReserve (requested_size, card_size * card_word_width, flags);
void *aligned_mem = prgmem;
// We don't want (prgmem + size) to be right at the end of the address space
@@ -6932,7 +6932,7 @@ uint32_t* gc_heap::make_card_table (uint8_t* start, uint8_t* end)
size_t alloc_size = sizeof (uint8_t)*(sizeof(card_table_info) + cs + bs + cb + wws + st + ms);
size_t alloc_size_aligned = Align (alloc_size, g_SystemInfo.dwAllocationGranularity-1);
- uint8_t* mem = (uint8_t*)GCToOSInterface::VirtualReserve (0, alloc_size_aligned, 0, virtual_reserve_flags);
+ uint8_t* mem = (uint8_t*)GCToOSInterface::VirtualReserve (alloc_size_aligned, 0, virtual_reserve_flags);
if (!mem)
return 0;
@@ -7125,7 +7125,7 @@ int gc_heap::grow_brick_card_tables (uint8_t* start,
dprintf (GC_TABLE_LOG, ("card table: %Id; brick table: %Id; card bundle: %Id; sw ww table: %Id; seg table: %Id; mark array: %Id",
cs, bs, cb, wws, st, ms));
- uint8_t* mem = (uint8_t*)GCToOSInterface::VirtualReserve (0, alloc_size_aligned, 0, virtual_reserve_flags);
+ uint8_t* mem = (uint8_t*)GCToOSInterface::VirtualReserve (alloc_size_aligned, 0, virtual_reserve_flags);
if (!mem)
{
@@ -9330,7 +9330,7 @@ void gc_heap::update_card_table_bundle()
bool success = GCToOSInterface::GetWriteWatch (false /* resetState */ , base_address, region_size,
(void**)g_addresses,
&bcount);
- assert (success);
+ assert (success && "GetWriteWatch failed!");
dprintf (3,("Found %d pages written", bcount));
for (unsigned i = 0; i < bcount; i++)
{
@@ -36656,7 +36656,7 @@ void initGCShadow()
if (len > (size_t)(g_GCShadowEnd - g_GCShadow))
{
deleteGCShadow();
- g_GCShadowEnd = g_GCShadow = (uint8_t *)GCToOSInterface::VirtualReserve(0, len, 0, VirtualReserveFlags::None);
+ g_GCShadowEnd = g_GCShadow = (uint8_t *)GCToOSInterface::VirtualReserve(len, 0, VirtualReserveFlags::None);
if (g_GCShadow == NULL || !GCToOSInterface::VirtualCommit(g_GCShadow, len))
{
_ASSERTE(!"Not enough memory to run HeapVerify level 2");
diff --git a/src/gc/gc.h b/src/gc/gc.h
index ca9c28d8fc..da047ad0c6 100644
--- a/src/gc/gc.h
+++ b/src/gc/gc.h
@@ -14,7 +14,19 @@ Module Name:
#ifndef __GC_H
#define __GC_H
+#ifdef Sleep
+// This is a funny workaround for the fact that "common.h" defines Sleep to be
+// Dont_Use_Sleep, with the hope of causing linker errors whenever someone tries to use sleep.
+//
+// However, GCToOSInterface defines a function called Sleep, which (due to this define) becomes
+// "Dont_Use_Sleep", which the GC in turn happily uses. The symbol that GCToOSInterface actually
+// exported was called "GCToOSInterface::Dont_Use_Sleep". While we progress in making the GC standalone,
+// we'll need to break the dependency on common.h (the VM header) and this problem will become moot.
+#undef Sleep
+#endif // Sleep
+
#include "gcinterface.h"
+#include "env/gcenv.os.h"
#include "env/gcenv.ee.h"
#ifdef FEATURE_STANDALONE_GC
diff --git a/src/gc/gcenv.unix.cpp b/src/gc/gcenv.unix.cpp
new file mode 100644
index 0000000000..8263ded173
--- /dev/null
+++ b/src/gc/gcenv.unix.cpp
@@ -0,0 +1,310 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#include "env/gcenv.structs.h"
+#include "env/gcenv.base.h"
+#include "env/gcenv.os.h"
+
+#error This file should not be compiled!
+
+// Initialize the interface implementation
+// Return:
+// true if it has succeeded, false if it has failed
+bool GCToOSInterface::Initialize()
+{
+ throw nullptr;
+}
+
+// Shutdown the interface implementation
+void GCToOSInterface::Shutdown()
+{
+ throw nullptr;
+}
+
+// Get numeric id of the current thread if possible on the
+// current platform. It is indended for logging purposes only.
+// Return:
+// Numeric id of the current thread or 0 if the
+uint64_t GCToOSInterface::GetCurrentThreadIdForLogging()
+{
+ throw nullptr;
+}
+
+// Get id of the process
+uint32_t GCToOSInterface::GetCurrentProcessId()
+{
+ throw nullptr;
+}
+
+// Set ideal affinity for the current thread
+// Parameters:
+// affinity - ideal processor affinity for the thread
+// Return:
+// true if it has succeeded, false if it has failed
+bool GCToOSInterface::SetCurrentThreadIdealAffinity(GCThreadAffinity* affinity)
+{
+ throw nullptr;
+}
+
+// Get the number of the current processor
+uint32_t GCToOSInterface::GetCurrentProcessorNumber()
+{
+ throw nullptr;
+}
+
+// Check if the OS supports getting current processor number
+bool GCToOSInterface::CanGetCurrentProcessorNumber()
+{
+ throw nullptr;
+}
+
+// Flush write buffers of processors that are executing threads of the current process
+void GCToOSInterface::FlushProcessWriteBuffers()
+{
+ throw nullptr;
+}
+
+// Break into a debugger
+void GCToOSInterface::DebugBreak()
+{
+ throw nullptr;
+}
+
+// Get number of logical processors
+uint32_t GCToOSInterface::GetLogicalCpuCount()
+{
+ throw nullptr;
+}
+
+// Causes the calling thread to sleep for the specified number of milliseconds
+// Parameters:
+// sleepMSec - time to sleep before switching to another thread
+void GCToOSInterface::Sleep(uint32_t sleepMSec)
+{
+ throw nullptr;
+}
+
+// Causes the calling thread to yield execution to another thread that is ready to run on the current processor.
+// Parameters:
+// switchCount - number of times the YieldThread was called in a loop
+void GCToOSInterface::YieldThread(uint32_t switchCount)
+{
+ throw nullptr;
+}
+
+// Reserve virtual memory range.
+// Parameters:
+// size - size of the virtual memory range
+// alignment - requested memory alignment, 0 means no specific alignment requested
+// flags - flags to control special settings like write watching
+// Return:
+// Starting virtual address of the reserved range
+void* GCToOSInterface::VirtualReserve(size_t size, size_t alignment, uint32_t flags)
+{
+ throw nullptr;
+}
+
+// Release virtual memory range previously reserved using VirtualReserve
+// Parameters:
+// address - starting virtual address
+// size - size of the virtual memory range
+// Return:
+// true if it has succeeded, false if it has failed
+bool GCToOSInterface::VirtualRelease(void* address, size_t size)
+{
+ throw nullptr;
+}
+
+// Commit virtual memory range. It must be part of a range reserved using VirtualReserve.
+// Parameters:
+// address - starting virtual address
+// size - size of the virtual memory range
+// Return:
+// true if it has succeeded, false if it has failed
+bool GCToOSInterface::VirtualCommit(void* address, size_t size)
+{
+ throw nullptr;
+}
+
+// Decomit virtual memory range.
+// Parameters:
+// address - starting virtual address
+// size - size of the virtual memory range
+// Return:
+// true if it has succeeded, false if it has failed
+bool GCToOSInterface::VirtualDecommit(void* address, size_t size)
+{
+ throw nullptr;
+}
+
+// Reset virtual memory range. Indicates that data in the memory range specified by address and size is no
+// longer of interest, but it should not be decommitted.
+// Parameters:
+// address - starting virtual address
+// size - size of the virtual memory range
+// unlock - true if the memory range should also be unlocked
+// Return:
+// true if it has succeeded, false if it has failed
+bool GCToOSInterface::VirtualReset(void * address, size_t size, bool unlock)
+{
+ throw nullptr;
+}
+
+// Check if the OS supports write watching
+bool GCToOSInterface::SupportsWriteWatch()
+{
+ throw nullptr;
+}
+
+// Reset the write tracking state for the specified virtual memory range.
+// Parameters:
+// address - starting virtual address
+// size - size of the virtual memory range
+void GCToOSInterface::ResetWriteWatch(void* address, size_t size)
+{
+ throw nullptr;
+}
+
+// Retrieve addresses of the pages that are written to in a region of virtual memory
+// Parameters:
+// resetState - true indicates to reset the write tracking state
+// address - starting virtual address
+// size - size of the virtual memory range
+// pageAddresses - buffer that receives an array of page addresses in the memory region
+// pageAddressesCount - on input, size of the lpAddresses array, in array elements
+// on output, the number of page addresses that are returned in the array.
+// Return:
+// true if it has succeeded, false if it has failed
+bool GCToOSInterface::GetWriteWatch(bool resetState, void* address, size_t size, void** pageAddresses, uintptr_t* pageAddressesCount)
+{
+ throw nullptr;
+}
+
+// Get size of the largest cache on the processor die
+// Parameters:
+// trueSize - true to return true cache size, false to return scaled up size based on
+// the processor architecture
+// Return:
+// Size of the cache
+size_t GCToOSInterface::GetLargestOnDieCacheSize(bool trueSize)
+{
+ throw nullptr;
+}
+
+// Get affinity mask of the current process
+// Parameters:
+// processMask - affinity mask for the specified process
+// systemMask - affinity mask for the system
+// Return:
+// true if it has succeeded, false if it has failed
+// Remarks:
+// A process affinity mask is a bit vector in which each bit represents the processors that
+// a process is allowed to run on. A system affinity mask is a bit vector in which each bit
+// represents the processors that are configured into a system.
+// A process affinity mask is a subset of the system affinity mask. A process is only allowed
+// to run on the processors configured into a system. Therefore, the process affinity mask cannot
+// specify a 1 bit for a processor when the system affinity mask specifies a 0 bit for that processor.
+bool GCToOSInterface::GetCurrentProcessAffinityMask(uintptr_t* processMask, uintptr_t* systemMask)
+{
+ throw nullptr;
+}
+
+// Get number of processors assigned to the current process
+// Return:
+// The number of processors
+uint32_t GCToOSInterface::GetCurrentProcessCpuCount()
+{
+ throw nullptr;
+}
+
+// Return the size of the user-mode portion of the virtual address space of this process.
+// Return:
+// non zero if it has succeeded, 0 if it has failed
+size_t GCToOSInterface::GetVirtualMemoryLimit()
+{
+ throw nullptr;
+}
+
+// Get the physical memory that this process can use.
+// Return:
+// non zero if it has succeeded, 0 if it has failed
+// Remarks:
+// If a process runs with a restricted memory limit, it returns the limit. If there's no limit
+// specified, it returns amount of actual physical memory.
+uint64_t GCToOSInterface::GetPhysicalMemoryLimit()
+{
+ throw nullptr;
+}
+
+// Get memory status
+// Parameters:
+// memory_load - A number between 0 and 100 that specifies the approximate percentage of physical memory
+// that is in use (0 indicates no memory use and 100 indicates full memory use).
+// available_physical - The amount of physical memory currently available, in bytes.
+// available_page_file - The maximum amount of memory the current process can commit, in bytes.
+void GCToOSInterface::GetMemoryStatus(uint32_t* memory_load, uint64_t* available_physical, uint64_t* available_page_file)
+{
+ throw nullptr;
+}
+
+// Get a high precision performance counter
+// Return:
+// The counter value
+int64_t GCToOSInterface::QueryPerformanceCounter()
+{
+ throw nullptr;
+}
+
+// Get a frequency of the high precision performance counter
+// Return:
+// The counter frequency
+int64_t GCToOSInterface::QueryPerformanceFrequency()
+{
+ throw nullptr;
+}
+
+// Get a time stamp with a low precision
+// Return:
+// Time stamp in milliseconds
+uint32_t GCToOSInterface::GetLowPrecisionTimeStamp()
+{
+ throw nullptr;
+}
+
+
+// Create a new thread for GC use
+// Parameters:
+// function - the function to be executed by the thread
+// param - parameters of the thread
+// affinity - processor affinity of the thread
+// Return:
+// true if it has succeeded, false if it has failed
+bool GCToOSInterface::CreateThread(GCThreadFunction function, void* param, GCThreadAffinity* affinity)
+{
+ throw nullptr;
+}
+
+// Initialize the critical section
+void CLRCriticalSection::Initialize()
+{
+ throw nullptr;
+}
+
+// Destroy the critical section
+void CLRCriticalSection::Destroy()
+{
+ throw nullptr;
+}
+
+// Enter the critical section. Blocks until the section can be entered.
+void CLRCriticalSection::Enter()
+{
+ throw nullptr;
+}
+
+// Leave the critical section
+void CLRCriticalSection::Leave()
+{
+ throw nullptr;
+} \ No newline at end of file
diff --git a/src/gc/gcenv.windows.cpp b/src/gc/gcenv.windows.cpp
new file mode 100644
index 0000000000..934daab0f3
--- /dev/null
+++ b/src/gc/gcenv.windows.cpp
@@ -0,0 +1,669 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#include <cstdint>
+#include <cassert>
+#include <cstddef>
+#include <memory>
+#include "windows.h"
+#include "psapi.h"
+#include "env/gcenv.structs.h"
+#include "env/gcenv.base.h"
+#include "env/gcenv.os.h"
+
+#ifndef FEATURE_STANDALONE_GC
+#error This file should only be compiled for a standalone GC
+#endif // FEATURE_STANDALONE_GC
+
+GCSystemInfo g_SystemInfo;
+
+typedef BOOL (WINAPI *PGET_PROCESS_MEMORY_INFO)(HANDLE handle, PROCESS_MEMORY_COUNTERS* memCounters, uint32_t cb);
+static PGET_PROCESS_MEMORY_INFO GCGetProcessMemoryInfo = 0;
+
+static size_t g_RestrictedPhysicalMemoryLimit = (size_t)UINTPTR_MAX;
+
+typedef BOOL (WINAPI *PIS_PROCESS_IN_JOB)(HANDLE processHandle, HANDLE jobHandle, BOOL* result);
+typedef BOOL (WINAPI *PQUERY_INFORMATION_JOB_OBJECT)(HANDLE jobHandle, JOBOBJECTINFOCLASS jobObjectInfoClass, void* lpJobObjectInfo, DWORD cbJobObjectInfoLength, LPDWORD lpReturnLength);
+
+namespace {
+
+void GetProcessMemoryLoad(LPMEMORYSTATUSEX pMSEX)
+{
+ pMSEX->dwLength = sizeof(MEMORYSTATUSEX);
+ BOOL fRet = ::GlobalMemoryStatusEx(pMSEX);
+ assert(fRet);
+
+ // If the machine has more RAM than virtual address limit, let us cap it.
+ // Our GC can never use more than virtual address limit.
+ if (pMSEX->ullAvailPhys > pMSEX->ullTotalVirtual)
+ {
+ pMSEX->ullAvailPhys = pMSEX->ullAvailVirtual;
+ }
+}
+
+#ifdef FEATURE_CORECLR
+
+inline bool RunningOnWin8()
+{
+ // TODO(segilles) platform detection
+ return false;
+}
+
+// For coresys we need to look for an API in some apiset dll on win8 if we can't find it
+// in the traditional dll.
+HINSTANCE LoadDllForAPI(const WCHAR* dllTraditional, const WCHAR* dllApiSet)
+{
+ HINSTANCE hinst = LoadLibraryEx(dllTraditional, nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
+
+ if (!hinst)
+ {
+ if(RunningOnWin8())
+ hinst = LoadLibraryEx(dllApiSet, nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
+ }
+
+ return hinst;
+}
+#endif
+
+static size_t GetRestrictedPhysicalMemoryLimit()
+{
+ LIMITED_METHOD_CONTRACT;
+
+ // The limit was cached already
+ if (g_RestrictedPhysicalMemoryLimit != (size_t)UINTPTR_MAX)
+ return g_RestrictedPhysicalMemoryLimit;
+
+ size_t job_physical_memory_limit = (size_t)UINTPTR_MAX;
+ BOOL in_job_p = FALSE;
+ HINSTANCE hinstApiSetPsapiOrKernel32 = 0;
+ // these 2 modules will need to be freed no matter what as we only use them locally in this method.
+ HINSTANCE hinstApiSetJob1OrKernel32 = 0;
+ HINSTANCE hinstApiSetJob2OrKernel32 = 0;
+
+ PIS_PROCESS_IN_JOB GCIsProcessInJob = 0;
+ PQUERY_INFORMATION_JOB_OBJECT GCQueryInformationJobObject = 0;
+
+ hinstApiSetJob1OrKernel32 = LoadDllForAPI(L"kernel32.dll", L"api-ms-win-core-job-l1-1-0.dll");
+ if (!hinstApiSetJob1OrKernel32)
+ goto exit;
+
+ GCIsProcessInJob = (PIS_PROCESS_IN_JOB)GetProcAddress(hinstApiSetJob1OrKernel32, "IsProcessInJob");
+ if (!GCIsProcessInJob)
+ goto exit;
+
+ if (!GCIsProcessInJob(GetCurrentProcess(), NULL, &in_job_p))
+ goto exit;
+
+ if (in_job_p)
+ {
+ hinstApiSetPsapiOrKernel32 = LoadDllForAPI(L"kernel32.dll", L"api-ms-win-core-psapi-l1-1-0");
+ if (!hinstApiSetPsapiOrKernel32)
+ goto exit;
+
+ GCGetProcessMemoryInfo = (PGET_PROCESS_MEMORY_INFO)GetProcAddress(hinstApiSetPsapiOrKernel32, "K32GetProcessMemoryInfo");
+
+ if (!GCGetProcessMemoryInfo)
+ goto exit;
+
+ hinstApiSetJob2OrKernel32 = LoadDllForAPI(L"kernel32.dll", L"api-ms-win-core-job-l2-1-0");
+ if (!hinstApiSetJob2OrKernel32)
+ goto exit;
+
+ GCQueryInformationJobObject = (PQUERY_INFORMATION_JOB_OBJECT)GetProcAddress(hinstApiSetJob2OrKernel32, "QueryInformationJobObject");
+
+ if (!GCQueryInformationJobObject)
+ goto exit;
+
+ JOBOBJECT_EXTENDED_LIMIT_INFORMATION limit_info;
+ if (GCQueryInformationJobObject (NULL, JobObjectExtendedLimitInformation, &limit_info,
+ sizeof(limit_info), NULL))
+ {
+ size_t job_memory_limit = (size_t)UINTPTR_MAX;
+ size_t job_process_memory_limit = (size_t)UINTPTR_MAX;
+ size_t job_workingset_limit = (size_t)UINTPTR_MAX;
+
+ // Notes on the NT job object:
+ //
+ // You can specific a bigger process commit or working set limit than
+ // job limit which is pointless so we use the smallest of all 3 as
+ // to calculate our "physical memory load" or "available physical memory"
+ // when running inside a job object, ie, we treat this as the amount of physical memory
+ // our process is allowed to use.
+ //
+ // The commit limit is already reflected by default when you run in a
+ // job but the physical memory load is not.
+ //
+ if ((limit_info.BasicLimitInformation.LimitFlags & JOB_OBJECT_LIMIT_JOB_MEMORY) != 0)
+ job_memory_limit = limit_info.JobMemoryLimit;
+ if ((limit_info.BasicLimitInformation.LimitFlags & JOB_OBJECT_LIMIT_PROCESS_MEMORY) != 0)
+ job_process_memory_limit = limit_info.ProcessMemoryLimit;
+ if ((limit_info.BasicLimitInformation.LimitFlags & JOB_OBJECT_LIMIT_WORKINGSET) != 0)
+ job_workingset_limit = limit_info.BasicLimitInformation.MaximumWorkingSetSize;
+
+ job_physical_memory_limit = min (job_memory_limit, job_process_memory_limit);
+ job_physical_memory_limit = min (job_physical_memory_limit, job_workingset_limit);
+
+ MEMORYSTATUSEX ms;
+ ::GetProcessMemoryLoad(&ms);
+
+ // A sanity check in case someone set a larger limit than there is actual physical memory.
+ job_physical_memory_limit = (size_t) min (job_physical_memory_limit, ms.ullTotalPhys);
+ }
+ }
+
+exit:
+ if (hinstApiSetJob1OrKernel32)
+ FreeLibrary(hinstApiSetJob1OrKernel32);
+ if (hinstApiSetJob2OrKernel32)
+ FreeLibrary(hinstApiSetJob2OrKernel32);
+
+ if (job_physical_memory_limit == (size_t)UINTPTR_MAX)
+ {
+ job_physical_memory_limit = 0;
+
+ FreeLibrary(hinstApiSetPsapiOrKernel32);
+ }
+
+ VolatileStore(&g_RestrictedPhysicalMemoryLimit, job_physical_memory_limit);
+ return g_RestrictedPhysicalMemoryLimit;
+}
+
+} // anonymous namespace
+
+// Initialize the interface implementation
+// Return:
+// true if it has succeeded, false if it has failed
+bool GCToOSInterface::Initialize()
+{
+ SYSTEM_INFO systemInfo;
+ GetSystemInfo(&systemInfo);
+
+ g_SystemInfo.dwNumberOfProcessors = systemInfo.dwNumberOfProcessors;
+ g_SystemInfo.dwPageSize = systemInfo.dwPageSize;
+ g_SystemInfo.dwAllocationGranularity = systemInfo.dwAllocationGranularity;
+
+ return true;
+}
+
+// Shutdown the interface implementation
+void GCToOSInterface::Shutdown()
+{
+ // nothing to do.
+}
+
+// Get numeric id of the current thread if possible on the
+// current platform. It is indended for logging purposes only.
+// Return:
+// Numeric id of the current thread or 0 if the
+uint64_t GCToOSInterface::GetCurrentThreadIdForLogging()
+{
+ return ::GetCurrentThreadId();
+}
+
+// Get id of the process
+uint32_t GCToOSInterface::GetCurrentProcessId()
+{
+ return ::GetCurrentThreadId();
+}
+
+// Set ideal affinity for the current thread
+// Parameters:
+// affinity - ideal processor affinity for the thread
+// Return:
+// true if it has succeeded, false if it has failed
+bool GCToOSInterface::SetCurrentThreadIdealAffinity(GCThreadAffinity* affinity)
+{
+ bool success = true;
+
+#if !defined(FEATURE_CORESYSTEM)
+ SetThreadIdealProcessor(GetCurrentThread(), (DWORD)affinity->Processor);
+#else
+ PROCESSOR_NUMBER proc;
+
+ if (affinity->Group != -1)
+ {
+ proc.Group = (WORD)affinity->Group;
+ proc.Number = (BYTE)affinity->Processor;
+ proc.Reserved = 0;
+
+ success = !!SetThreadIdealProcessorEx(GetCurrentThread(), &proc, NULL);
+ }
+ else
+ {
+ if (GetThreadIdealProcessorEx(GetCurrentThread(), &proc))
+ {
+ proc.Number = affinity->Processor;
+ success = !!SetThreadIdealProcessorEx(GetCurrentThread(), &proc, NULL);
+ }
+ }
+#endif
+
+ return success;
+}
+
+// Get the number of the current processor
+uint32_t GCToOSInterface::GetCurrentProcessorNumber()
+{
+ assert(GCToOSInterface::CanGetCurrentProcessorNumber());
+ return ::GetCurrentProcessorNumber();
+}
+
+// Check if the OS supports getting current processor number
+bool GCToOSInterface::CanGetCurrentProcessorNumber()
+{
+ // on all Windows platforms we support this API exists
+ return true;
+}
+
+// Flush write buffers of processors that are executing threads of the current process
+void GCToOSInterface::FlushProcessWriteBuffers()
+{
+ ::FlushProcessWriteBuffers();
+}
+
+// Break into a debugger
+void GCToOSInterface::DebugBreak()
+{
+ ::DebugBreak();
+}
+
+// Get number of logical processors
+uint32_t GCToOSInterface::GetLogicalCpuCount()
+{
+ // TODO(segilles) processor detection
+ return 1;
+}
+
+// Causes the calling thread to sleep for the specified number of milliseconds
+// Parameters:
+// sleepMSec - time to sleep before switching to another thread
+void GCToOSInterface::Sleep(uint32_t sleepMSec)
+{
+ // TODO(segilles) CLR implementation of __SwitchToThread spins for short sleep durations
+ // to avoid context switches - is that interesting or useful here?
+ if (sleepMSec > 0)
+ {
+ ::SleepEx(sleepMSec, FALSE);
+ }
+}
+
+// Causes the calling thread to yield execution to another thread that is ready to run on the current processor.
+// Parameters:
+// switchCount - number of times the YieldThread was called in a loop
+void GCToOSInterface::YieldThread(uint32_t switchCount)
+{
+ UNREFERENCED_PARAMETER(switchCount);
+ SwitchToThread();
+}
+
+// Reserve virtual memory range.
+// Parameters:
+// address - starting virtual address, it can be NULL to let the function choose the starting address
+// size - size of the virtual memory range
+// alignment - requested memory alignment, 0 means no specific alignment requested
+// flags - flags to control special settings like write watching
+// Return:
+// Starting virtual address of the reserved range
+void* GCToOSInterface::VirtualReserve(size_t size, size_t alignment, uint32_t flags)
+{
+ // Windows already ensures 64kb alignment on VirtualAlloc. The current CLR
+ // implementation ignores it on Windows, other than making some sanity checks on it.
+ UNREFERENCED_PARAMETER(alignment);
+ assert((alignment & (alignment - 1)) == 0);
+ assert(alignment <= 0x10000);
+ DWORD memFlags = (flags & VirtualReserveFlags::WriteWatch) ? (MEM_RESERVE | MEM_WRITE_WATCH) : MEM_RESERVE;
+ return ::VirtualAlloc(nullptr, size, memFlags, PAGE_READWRITE);
+}
+
+// Release virtual memory range previously reserved using VirtualReserve
+// Parameters:
+// address - starting virtual address
+// size - size of the virtual memory range
+// Return:
+// true if it has succeeded, false if it has failed
+bool GCToOSInterface::VirtualRelease(void* address, size_t size)
+{
+ return !!::VirtualFree(address, 0, MEM_RELEASE);
+}
+
+// Commit virtual memory range. It must be part of a range reserved using VirtualReserve.
+// Parameters:
+// address - starting virtual address
+// size - size of the virtual memory range
+// Return:
+// true if it has succeeded, false if it has failed
+bool GCToOSInterface::VirtualCommit(void* address, size_t size)
+{
+ return ::VirtualAlloc(address, size, MEM_COMMIT, PAGE_READWRITE) != nullptr;
+}
+
+// Decomit virtual memory range.
+// Parameters:
+// address - starting virtual address
+// size - size of the virtual memory range
+// Return:
+// true if it has succeeded, false if it has failed
+bool GCToOSInterface::VirtualDecommit(void* address, size_t size)
+{
+ return !!::VirtualFree(address, size, MEM_DECOMMIT);
+}
+
+// Reset virtual memory range. Indicates that data in the memory range specified by address and size is no
+// longer of interest, but it should not be decommitted.
+// Parameters:
+// address - starting virtual address
+// size - size of the virtual memory range
+// unlock - true if the memory range should also be unlocked
+// Return:
+// true if it has succeeded, false if it has failed. Returns false also if
+// unlocking was requested but the unlock failed.
+bool GCToOSInterface::VirtualReset(void * address, size_t size, bool unlock)
+{
+ bool success = ::VirtualAlloc(address, size, MEM_RESET, PAGE_READWRITE) != nullptr;
+ if (success && unlock)
+ {
+ ::VirtualUnlock(address, size);
+ }
+
+ return success;
+}
+
+// Check if the OS supports write watching
+bool GCToOSInterface::SupportsWriteWatch()
+{
+ void* mem = GCToOSInterface::VirtualReserve(g_SystemInfo.dwAllocationGranularity, 0, VirtualReserveFlags::WriteWatch);
+ if (mem != nullptr)
+ {
+ GCToOSInterface::VirtualRelease(mem, g_SystemInfo.dwAllocationGranularity);
+ return true;
+ }
+
+ return false;
+}
+
+// Reset the write tracking state for the specified virtual memory range.
+// Parameters:
+// address - starting virtual address
+// size - size of the virtual memory range
+void GCToOSInterface::ResetWriteWatch(void* address, size_t size)
+{
+ ::ResetWriteWatch(address, size);
+}
+
+// Retrieve addresses of the pages that are written to in a region of virtual memory
+// Parameters:
+// resetState - true indicates to reset the write tracking state
+// address - starting virtual address
+// size - size of the virtual memory range
+// pageAddresses - buffer that receives an array of page addresses in the memory region
+// pageAddressesCount - on input, size of the lpAddresses array, in array elements
+// on output, the number of page addresses that are returned in the array.
+// Return:
+// true if it has succeeded, false if it has failed
+bool GCToOSInterface::GetWriteWatch(bool resetState, void* address, size_t size, void** pageAddresses, uintptr_t* pageAddressesCount)
+{
+ uint32_t flags = resetState ? 1 : 0;
+ ULONG granularity;
+
+ bool success = ::GetWriteWatch(flags, address, size, pageAddresses, (ULONG_PTR*)pageAddressesCount, &granularity) == 0;
+ if (success)
+ {
+ assert(granularity == OS_PAGE_SIZE);
+ }
+
+ return success;
+}
+
+// Get size of the largest cache on the processor die
+// Parameters:
+// trueSize - true to return true cache size, false to return scaled up size based on
+// the processor architecture
+// Return:
+// Size of the cache
+size_t GCToOSInterface::GetLargestOnDieCacheSize(bool trueSize)
+{
+ // TODO(segilles) processor detection (see src/vm/util.cpp:1935)
+ return 0;
+}
+
+// Get affinity mask of the current process
+// Parameters:
+// processMask - affinity mask for the specified process
+// systemMask - affinity mask for the system
+// Return:
+// true if it has succeeded, false if it has failed
+// Remarks:
+// A process affinity mask is a bit vector in which each bit represents the processors that
+// a process is allowed to run on. A system affinity mask is a bit vector in which each bit
+// represents the processors that are configured into a system.
+// A process affinity mask is a subset of the system affinity mask. A process is only allowed
+// to run on the processors configured into a system. Therefore, the process affinity mask cannot
+// specify a 1 bit for a processor when the system affinity mask specifies a 0 bit for that processor.
+bool GCToOSInterface::GetCurrentProcessAffinityMask(uintptr_t* processMask, uintptr_t* systemMask)
+{
+ return !!::GetProcessAffinityMask(::GetCurrentProcess(), (PDWORD_PTR)processMask, (PDWORD_PTR)systemMask);
+}
+
+// Get number of processors assigned to the current process
+// Return:
+// The number of processors
+uint32_t GCToOSInterface::GetCurrentProcessCpuCount()
+{
+ // TODO(segilles) this does not take into account process affinity
+ return g_SystemInfo.dwNumberOfProcessors;
+}
+
+// Return the size of the user-mode portion of the virtual address space of this process.
+// Return:
+// non zero if it has succeeded, 0 if it has failed
+size_t GCToOSInterface::GetVirtualMemoryLimit()
+{
+ MEMORYSTATUSEX memStatus;
+ if (::GlobalMemoryStatusEx(&memStatus))
+ {
+ return memStatus.ullAvailVirtual;
+ }
+
+ return 0;
+}
+
+// Get the physical memory that this process can use.
+// Return:
+// non zero if it has succeeded, 0 if it has failed
+// Remarks:
+// If a process runs with a restricted memory limit, it returns the limit. If there's no limit
+// specified, it returns amount of actual physical memory.
+uint64_t GCToOSInterface::GetPhysicalMemoryLimit()
+{
+ size_t restricted_limit = GetRestrictedPhysicalMemoryLimit();
+ if (restricted_limit != 0)
+ return restricted_limit;
+
+ MEMORYSTATUSEX memStatus;
+ if (::GlobalMemoryStatusEx(&memStatus))
+ {
+ return memStatus.ullTotalPhys;
+ }
+
+ return 0;
+}
+
+// Get memory status
+// Parameters:
+// memory_load - A number between 0 and 100 that specifies the approximate percentage of physical memory
+// that is in use (0 indicates no memory use and 100 indicates full memory use).
+// available_physical - The amount of physical memory currently available, in bytes.
+// available_page_file - The maximum amount of memory the current process can commit, in bytes.
+void GCToOSInterface::GetMemoryStatus(uint32_t* memory_load, uint64_t* available_physical, uint64_t* available_page_file)
+{
+ uint64_t restricted_limit = GetRestrictedPhysicalMemoryLimit();
+ if (restricted_limit != 0)
+ {
+ PROCESS_MEMORY_COUNTERS pmc;
+ if (GCGetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc)))
+ {
+ if (memory_load)
+ *memory_load = (uint32_t)((float)pmc.WorkingSetSize * 100.0 / (float)restricted_limit);
+ if (available_physical)
+ *available_physical = restricted_limit - pmc.WorkingSetSize;
+ // Available page file doesn't mean much when physical memory is restricted since
+ // we don't know how much of it is available to this process so we are not going to
+ // bother to make another OS call for it.
+ if (available_page_file)
+ *available_page_file = 0;
+
+ return;
+ }
+ }
+
+ MEMORYSTATUSEX ms;
+ ::GetProcessMemoryLoad(&ms);
+
+ if (memory_load != nullptr)
+ *memory_load = ms.dwMemoryLoad;
+ if (available_physical != nullptr)
+ *available_physical = ms.ullAvailPhys;
+ if (available_page_file != nullptr)
+ *available_page_file = ms.ullAvailPageFile;
+}
+
+// Get a high precision performance counter
+// Return:
+// The counter value
+int64_t GCToOSInterface::QueryPerformanceCounter()
+{
+ LARGE_INTEGER ts;
+ if (!::QueryPerformanceCounter(&ts))
+ {
+ assert(false && "Failed to query performance counter");
+ }
+
+ return ts.QuadPart;
+}
+
+// Get a frequency of the high precision performance counter
+// Return:
+// The counter frequency
+int64_t GCToOSInterface::QueryPerformanceFrequency()
+{
+ LARGE_INTEGER ts;
+ if (!::QueryPerformanceFrequency(&ts))
+ {
+ assert(false && "Failed to query performance counter");
+ }
+
+ return ts.QuadPart;
+}
+
+// Get a time stamp with a low precision
+// Return:
+// Time stamp in milliseconds
+uint32_t GCToOSInterface::GetLowPrecisionTimeStamp()
+{
+ return ::GetTickCount();
+}
+
+// Parameters of the GC thread stub
+struct GCThreadStubParam
+{
+ GCThreadFunction GCThreadFunction;
+ void* GCThreadParam;
+};
+
+// GC thread stub to convert GC thread function to an OS specific thread function
+static DWORD GCThreadStub(void* param)
+{
+ GCThreadStubParam *stubParam = (GCThreadStubParam*)param;
+ GCThreadFunction function = stubParam->GCThreadFunction;
+ void* threadParam = stubParam->GCThreadParam;
+
+ delete stubParam;
+
+ function(threadParam);
+
+ return 0;
+}
+
+
+// Create a new thread for GC use
+// Parameters:
+// function - the function to be executed by the thread
+// param - parameters of the thread
+// affinity - processor affinity of the thread
+// Return:
+// true if it has succeeded, false if it has failed
+bool GCToOSInterface::CreateThread(GCThreadFunction function, void* param, GCThreadAffinity* affinity)
+{
+ uint32_t thread_id;
+
+ std::unique_ptr<GCThreadStubParam> stubParam(new (std::nothrow) GCThreadStubParam());
+ if (!stubParam)
+ {
+ return false;
+ }
+
+ stubParam->GCThreadFunction = function;
+ stubParam->GCThreadParam = param;
+
+ HANDLE gc_thread = ::CreateThread(
+ nullptr,
+ 512 * 1024 /* Thread::StackSize_Medium */,
+ (LPTHREAD_START_ROUTINE)GCThreadStub,
+ stubParam.get(),
+ CREATE_SUSPENDED | STACK_SIZE_PARAM_IS_A_RESERVATION,
+ (DWORD*)&thread_id);
+
+ if (!gc_thread)
+ {
+ return false;
+ }
+
+ stubParam.release();
+ bool result = !!::SetThreadPriority(gc_thread, /* THREAD_PRIORITY_ABOVE_NORMAL );*/ THREAD_PRIORITY_HIGHEST );
+ assert(result && "failed to set thread priority");
+
+ if (affinity->Group != GCThreadAffinity::None)
+ {
+ assert(affinity->Processor != GCThreadAffinity::None);
+ GROUP_AFFINITY ga;
+ ga.Group = (WORD)affinity->Group;
+ ga.Reserved[0] = 0; // reserve must be filled with zero
+ ga.Reserved[1] = 0; // otherwise call may fail
+ ga.Reserved[2] = 0;
+ ga.Mask = (size_t)1 << affinity->Processor;
+
+ bool result = !!::SetThreadGroupAffinity(gc_thread, &ga, nullptr);
+ assert(result && "failed to set thread affinity");
+ }
+ else if (affinity->Processor != GCThreadAffinity::None)
+ {
+ ::SetThreadAffinityMask(gc_thread, (DWORD_PTR)1 << affinity->Processor);
+ }
+
+ return true;
+}
+
+// Initialize the critical section
+void CLRCriticalSection::Initialize()
+{
+ ::InitializeCriticalSection(&m_cs);
+}
+
+// Destroy the critical section
+void CLRCriticalSection::Destroy()
+{
+ ::DeleteCriticalSection(&m_cs);
+}
+
+// Enter the critical section. Blocks until the section can be entered.
+void CLRCriticalSection::Enter()
+{
+ ::EnterCriticalSection(&m_cs);
+}
+
+// Leave the critical section
+void CLRCriticalSection::Leave()
+{
+ ::LeaveCriticalSection(&m_cs);
+}
diff --git a/src/gc/gcpriv.h b/src/gc/gcpriv.h
index e0147c33c7..364b0045a4 100644
--- a/src/gc/gcpriv.h
+++ b/src/gc/gcpriv.h
@@ -24,7 +24,9 @@
inline void FATAL_GC_ERROR()
{
+#ifndef DACCESS_COMPILE
GCToOSInterface::DebugBreak();
+#endif // DACCESS_COMPILE
_ASSERTE(!"Fatal Error in GC.");
EEPOLICY_HANDLE_FATAL_ERROR(COR_E_EXECUTIONENGINE);
}
diff --git a/src/gc/handletablecache.cpp b/src/gc/handletablecache.cpp
index b2af40c829..aaf3370bd6 100644
--- a/src/gc/handletablecache.cpp
+++ b/src/gc/handletablecache.cpp
@@ -15,6 +15,12 @@
#include "gcenv.h"
+#ifdef Sleep // TODO(segilles)
+#undef Sleep
+#endif // Sleep
+
+#include "env/gcenv.os.h"
+
#include "handletablepriv.h"
/****************************************************************************
diff --git a/src/gc/handletablecore.cpp b/src/gc/handletablecore.cpp
index be65b142b4..5776c26ace 100644
--- a/src/gc/handletablecore.cpp
+++ b/src/gc/handletablecore.cpp
@@ -611,7 +611,7 @@ TableSegment *SegmentAlloc(HandleTable *pTable)
_ASSERTE(HANDLE_SEGMENT_ALIGNMENT >= HANDLE_SEGMENT_SIZE);
_ASSERTE(HANDLE_SEGMENT_ALIGNMENT == 0x10000);
- pSegment = (TableSegment *)GCToOSInterface::VirtualReserve(NULL, HANDLE_SEGMENT_SIZE, HANDLE_SEGMENT_ALIGNMENT, VirtualReserveFlags::None);
+ pSegment = (TableSegment *)GCToOSInterface::VirtualReserve(HANDLE_SEGMENT_SIZE, HANDLE_SEGMENT_ALIGNMENT, VirtualReserveFlags::None);
_ASSERTE(((size_t)pSegment % HANDLE_SEGMENT_ALIGNMENT) == 0);
// bail out if we couldn't get any memory
diff --git a/src/gc/sample/gcenv.windows.cpp b/src/gc/sample/gcenv.windows.cpp
index 76187f2185..a14019df7c 100644
--- a/src/gc/sample/gcenv.windows.cpp
+++ b/src/gc/sample/gcenv.windows.cpp
@@ -155,7 +155,7 @@ void GCToOSInterface::YieldThread(uint32_t switchCount)
// flags - flags to control special settings like write watching
// Return:
// Starting virtual address of the reserved range
-void* GCToOSInterface::VirtualReserve(void* address, size_t size, size_t alignment, uint32_t flags)
+void* GCToOSInterface::VirtualReserve(size_t size, size_t alignment, uint32_t flags)
{
DWORD memFlags = (flags & VirtualReserveFlags::WriteWatch) ? (MEM_RESERVE | MEM_WRITE_WATCH) : MEM_RESERVE;
return ::VirtualAlloc(0, size, memFlags, PAGE_READWRITE);
diff --git a/src/gc/softwarewritewatch.cpp b/src/gc/softwarewritewatch.cpp
index 519744900b..fa14a04897 100644
--- a/src/gc/softwarewritewatch.cpp
+++ b/src/gc/softwarewritewatch.cpp
@@ -6,6 +6,7 @@
#include "softwarewritewatch.h"
#include "gcenv.h"
+#include "env/gcenv.os.h"
#ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP
#ifndef DACCESS_COMPILE