From bb9f30e4658342f83bb3b5639ae24ec92e0a1d2a Mon Sep 17 00:00:00 2001 From: Sean Gillespie Date: Wed, 16 Nov 2016 09:34:57 -0800 Subject: 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 --- src/gc/CMakeLists.txt | 12 + src/gc/env/gcenv.os.h | 3 +- src/gc/gc.cpp | 10 +- src/gc/gc.h | 12 + src/gc/gcenv.unix.cpp | 310 +++++++++++++++++++ src/gc/gcenv.windows.cpp | 669 ++++++++++++++++++++++++++++++++++++++++ src/gc/gcpriv.h | 2 + src/gc/handletablecache.cpp | 6 + src/gc/handletablecore.cpp | 2 +- src/gc/sample/gcenv.windows.cpp | 2 +- src/gc/softwarewritewatch.cpp | 1 + 11 files changed, 1020 insertions(+), 9 deletions(-) create mode 100644 src/gc/gcenv.unix.cpp create mode 100644 src/gc/gcenv.windows.cpp (limited to 'src/gc') 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 +#include +#include +#include +#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 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 -- cgit v1.2.3