1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
11 #include "env/gcenv.structs.h"
12 #include "env/gcenv.base.h"
13 #include "env/gcenv.os.h"
15 GCSystemInfo g_SystemInfo;
17 typedef BOOL (WINAPI *PGET_PROCESS_MEMORY_INFO)(HANDLE handle, PROCESS_MEMORY_COUNTERS* memCounters, uint32_t cb);
18 static PGET_PROCESS_MEMORY_INFO GCGetProcessMemoryInfo = 0;
20 static size_t g_RestrictedPhysicalMemoryLimit = (size_t)UINTPTR_MAX;
22 typedef BOOL (WINAPI *PIS_PROCESS_IN_JOB)(HANDLE processHandle, HANDLE jobHandle, BOOL* result);
23 typedef BOOL (WINAPI *PQUERY_INFORMATION_JOB_OBJECT)(HANDLE jobHandle, JOBOBJECTINFOCLASS jobObjectInfoClass, void* lpJobObjectInfo, DWORD cbJobObjectInfoLength, LPDWORD lpReturnLength);
27 void GetProcessMemoryLoad(LPMEMORYSTATUSEX pMSEX)
29 pMSEX->dwLength = sizeof(MEMORYSTATUSEX);
30 BOOL fRet = ::GlobalMemoryStatusEx(pMSEX);
33 // If the machine has more RAM than virtual address limit, let us cap it.
34 // Our GC can never use more than virtual address limit.
35 if (pMSEX->ullAvailPhys > pMSEX->ullTotalVirtual)
37 pMSEX->ullAvailPhys = pMSEX->ullAvailVirtual;
41 static size_t GetRestrictedPhysicalMemoryLimit()
43 LIMITED_METHOD_CONTRACT;
45 // The limit was cached already
46 if (g_RestrictedPhysicalMemoryLimit != (size_t)UINTPTR_MAX)
47 return g_RestrictedPhysicalMemoryLimit;
49 size_t job_physical_memory_limit = (size_t)UINTPTR_MAX;
50 BOOL in_job_p = FALSE;
51 HINSTANCE hinstKernel32 = 0;
53 PIS_PROCESS_IN_JOB GCIsProcessInJob = 0;
54 PQUERY_INFORMATION_JOB_OBJECT GCQueryInformationJobObject = 0;
56 hinstKernel32 = LoadLibraryEx(L"kernel32.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
60 GCIsProcessInJob = (PIS_PROCESS_IN_JOB)GetProcAddress(hinstKernel32, "IsProcessInJob");
61 if (!GCIsProcessInJob)
64 if (!GCIsProcessInJob(GetCurrentProcess(), NULL, &in_job_p))
69 GCGetProcessMemoryInfo = (PGET_PROCESS_MEMORY_INFO)GetProcAddress(hinstKernel32, "K32GetProcessMemoryInfo");
71 if (!GCGetProcessMemoryInfo)
74 GCQueryInformationJobObject = (PQUERY_INFORMATION_JOB_OBJECT)GetProcAddress(hinstKernel32, "QueryInformationJobObject");
76 if (!GCQueryInformationJobObject)
79 JOBOBJECT_EXTENDED_LIMIT_INFORMATION limit_info;
80 if (GCQueryInformationJobObject (NULL, JobObjectExtendedLimitInformation, &limit_info,
81 sizeof(limit_info), NULL))
83 size_t job_memory_limit = (size_t)UINTPTR_MAX;
84 size_t job_process_memory_limit = (size_t)UINTPTR_MAX;
85 size_t job_workingset_limit = (size_t)UINTPTR_MAX;
87 // Notes on the NT job object:
89 // You can specific a bigger process commit or working set limit than
90 // job limit which is pointless so we use the smallest of all 3 as
91 // to calculate our "physical memory load" or "available physical memory"
92 // when running inside a job object, ie, we treat this as the amount of physical memory
93 // our process is allowed to use.
95 // The commit limit is already reflected by default when you run in a
96 // job but the physical memory load is not.
98 if ((limit_info.BasicLimitInformation.LimitFlags & JOB_OBJECT_LIMIT_JOB_MEMORY) != 0)
99 job_memory_limit = limit_info.JobMemoryLimit;
100 if ((limit_info.BasicLimitInformation.LimitFlags & JOB_OBJECT_LIMIT_PROCESS_MEMORY) != 0)
101 job_process_memory_limit = limit_info.ProcessMemoryLimit;
102 if ((limit_info.BasicLimitInformation.LimitFlags & JOB_OBJECT_LIMIT_WORKINGSET) != 0)
103 job_workingset_limit = limit_info.BasicLimitInformation.MaximumWorkingSetSize;
105 job_physical_memory_limit = min (job_memory_limit, job_process_memory_limit);
106 job_physical_memory_limit = min (job_physical_memory_limit, job_workingset_limit);
109 ::GetProcessMemoryLoad(&ms);
111 // A sanity check in case someone set a larger limit than there is actual physical memory.
112 job_physical_memory_limit = (size_t) min (job_physical_memory_limit, ms.ullTotalPhys);
117 if (job_physical_memory_limit == (size_t)UINTPTR_MAX)
119 job_physical_memory_limit = 0;
121 FreeLibrary(hinstKernel32);
124 VolatileStore(&g_RestrictedPhysicalMemoryLimit, job_physical_memory_limit);
125 return g_RestrictedPhysicalMemoryLimit;
128 } // anonymous namespace
130 // Initialize the interface implementation
132 // true if it has succeeded, false if it has failed
133 bool GCToOSInterface::Initialize()
135 SYSTEM_INFO systemInfo;
136 GetSystemInfo(&systemInfo);
138 g_SystemInfo.dwNumberOfProcessors = systemInfo.dwNumberOfProcessors;
139 g_SystemInfo.dwPageSize = systemInfo.dwPageSize;
140 g_SystemInfo.dwAllocationGranularity = systemInfo.dwAllocationGranularity;
145 // Shutdown the interface implementation
146 void GCToOSInterface::Shutdown()
151 // Get numeric id of the current thread if possible on the
152 // current platform. It is indended for logging purposes only.
154 // Numeric id of the current thread or 0 if the
155 uint64_t GCToOSInterface::GetCurrentThreadIdForLogging()
157 return ::GetCurrentThreadId();
160 // Get id of the process
161 uint32_t GCToOSInterface::GetCurrentProcessId()
163 return ::GetCurrentThreadId();
166 // Set ideal affinity for the current thread
168 // affinity - ideal processor affinity for the thread
170 // true if it has succeeded, false if it has failed
171 bool GCToOSInterface::SetCurrentThreadIdealAffinity(GCThreadAffinity* affinity)
175 #if !defined(FEATURE_CORESYSTEM)
176 SetThreadIdealProcessor(GetCurrentThread(), (DWORD)affinity->Processor);
178 PROCESSOR_NUMBER proc;
180 if (affinity->Group != -1)
182 proc.Group = (WORD)affinity->Group;
183 proc.Number = (BYTE)affinity->Processor;
186 success = !!SetThreadIdealProcessorEx(GetCurrentThread(), &proc, NULL);
190 if (GetThreadIdealProcessorEx(GetCurrentThread(), &proc))
192 proc.Number = affinity->Processor;
193 success = !!SetThreadIdealProcessorEx(GetCurrentThread(), &proc, NULL);
201 // Get the number of the current processor
202 uint32_t GCToOSInterface::GetCurrentProcessorNumber()
204 assert(GCToOSInterface::CanGetCurrentProcessorNumber());
205 return ::GetCurrentProcessorNumber();
208 // Check if the OS supports getting current processor number
209 bool GCToOSInterface::CanGetCurrentProcessorNumber()
211 // on all Windows platforms we support this API exists
215 // Flush write buffers of processors that are executing threads of the current process
216 void GCToOSInterface::FlushProcessWriteBuffers()
218 ::FlushProcessWriteBuffers();
221 // Break into a debugger
222 void GCToOSInterface::DebugBreak()
227 // Get number of logical processors
228 uint32_t GCToOSInterface::GetLogicalCpuCount()
230 // TODO(segilles) processor detection
234 // Causes the calling thread to sleep for the specified number of milliseconds
236 // sleepMSec - time to sleep before switching to another thread
237 void GCToOSInterface::Sleep(uint32_t sleepMSec)
239 // TODO(segilles) CLR implementation of __SwitchToThread spins for short sleep durations
240 // to avoid context switches - is that interesting or useful here?
243 ::SleepEx(sleepMSec, FALSE);
247 // Causes the calling thread to yield execution to another thread that is ready to run on the current processor.
249 // switchCount - number of times the YieldThread was called in a loop
250 void GCToOSInterface::YieldThread(uint32_t switchCount)
252 UNREFERENCED_PARAMETER(switchCount);
256 // Reserve virtual memory range.
258 // address - starting virtual address, it can be NULL to let the function choose the starting address
259 // size - size of the virtual memory range
260 // alignment - requested memory alignment, 0 means no specific alignment requested
261 // flags - flags to control special settings like write watching
263 // Starting virtual address of the reserved range
264 void* GCToOSInterface::VirtualReserve(size_t size, size_t alignment, uint32_t flags)
266 // Windows already ensures 64kb alignment on VirtualAlloc. The current CLR
267 // implementation ignores it on Windows, other than making some sanity checks on it.
268 UNREFERENCED_PARAMETER(alignment);
269 assert((alignment & (alignment - 1)) == 0);
270 assert(alignment <= 0x10000);
271 DWORD memFlags = (flags & VirtualReserveFlags::WriteWatch) ? (MEM_RESERVE | MEM_WRITE_WATCH) : MEM_RESERVE;
272 return ::VirtualAlloc(nullptr, size, memFlags, PAGE_READWRITE);
275 // Release virtual memory range previously reserved using VirtualReserve
277 // address - starting virtual address
278 // size - size of the virtual memory range
280 // true if it has succeeded, false if it has failed
281 bool GCToOSInterface::VirtualRelease(void* address, size_t size)
283 return !!::VirtualFree(address, 0, MEM_RELEASE);
286 // Commit virtual memory range. It must be part of a range reserved using VirtualReserve.
288 // address - starting virtual address
289 // size - size of the virtual memory range
291 // true if it has succeeded, false if it has failed
292 bool GCToOSInterface::VirtualCommit(void* address, size_t size)
294 return ::VirtualAlloc(address, size, MEM_COMMIT, PAGE_READWRITE) != nullptr;
297 // Decomit virtual memory range.
299 // address - starting virtual address
300 // size - size of the virtual memory range
302 // true if it has succeeded, false if it has failed
303 bool GCToOSInterface::VirtualDecommit(void* address, size_t size)
305 return !!::VirtualFree(address, size, MEM_DECOMMIT);
308 // Reset virtual memory range. Indicates that data in the memory range specified by address and size is no
309 // longer of interest, but it should not be decommitted.
311 // address - starting virtual address
312 // size - size of the virtual memory range
313 // unlock - true if the memory range should also be unlocked
315 // true if it has succeeded, false if it has failed. Returns false also if
316 // unlocking was requested but the unlock failed.
317 bool GCToOSInterface::VirtualReset(void * address, size_t size, bool unlock)
319 bool success = ::VirtualAlloc(address, size, MEM_RESET, PAGE_READWRITE) != nullptr;
320 if (success && unlock)
322 ::VirtualUnlock(address, size);
328 // Check if the OS supports write watching
329 bool GCToOSInterface::SupportsWriteWatch()
331 void* mem = GCToOSInterface::VirtualReserve(g_SystemInfo.dwAllocationGranularity, 0, VirtualReserveFlags::WriteWatch);
334 GCToOSInterface::VirtualRelease(mem, g_SystemInfo.dwAllocationGranularity);
341 // Reset the write tracking state for the specified virtual memory range.
343 // address - starting virtual address
344 // size - size of the virtual memory range
345 void GCToOSInterface::ResetWriteWatch(void* address, size_t size)
347 ::ResetWriteWatch(address, size);
350 // Retrieve addresses of the pages that are written to in a region of virtual memory
352 // resetState - true indicates to reset the write tracking state
353 // address - starting virtual address
354 // size - size of the virtual memory range
355 // pageAddresses - buffer that receives an array of page addresses in the memory region
356 // pageAddressesCount - on input, size of the lpAddresses array, in array elements
357 // on output, the number of page addresses that are returned in the array.
359 // true if it has succeeded, false if it has failed
360 bool GCToOSInterface::GetWriteWatch(bool resetState, void* address, size_t size, void** pageAddresses, uintptr_t* pageAddressesCount)
362 uint32_t flags = resetState ? 1 : 0;
365 bool success = ::GetWriteWatch(flags, address, size, pageAddresses, (ULONG_PTR*)pageAddressesCount, &granularity) == 0;
368 assert(granularity == OS_PAGE_SIZE);
374 // Get size of the largest cache on the processor die
376 // trueSize - true to return true cache size, false to return scaled up size based on
377 // the processor architecture
380 size_t GCToOSInterface::GetLargestOnDieCacheSize(bool trueSize)
382 // TODO(segilles) processor detection (see src/vm/util.cpp:1935)
386 // Get affinity mask of the current process
388 // processMask - affinity mask for the specified process
389 // systemMask - affinity mask for the system
391 // true if it has succeeded, false if it has failed
393 // A process affinity mask is a bit vector in which each bit represents the processors that
394 // a process is allowed to run on. A system affinity mask is a bit vector in which each bit
395 // represents the processors that are configured into a system.
396 // A process affinity mask is a subset of the system affinity mask. A process is only allowed
397 // to run on the processors configured into a system. Therefore, the process affinity mask cannot
398 // specify a 1 bit for a processor when the system affinity mask specifies a 0 bit for that processor.
399 bool GCToOSInterface::GetCurrentProcessAffinityMask(uintptr_t* processMask, uintptr_t* systemMask)
401 return !!::GetProcessAffinityMask(::GetCurrentProcess(), (PDWORD_PTR)processMask, (PDWORD_PTR)systemMask);
404 // Get number of processors assigned to the current process
406 // The number of processors
407 uint32_t GCToOSInterface::GetCurrentProcessCpuCount()
409 // TODO(segilles) this does not take into account process affinity
410 return g_SystemInfo.dwNumberOfProcessors;
413 // Return the size of the user-mode portion of the virtual address space of this process.
415 // non zero if it has succeeded, 0 if it has failed
416 size_t GCToOSInterface::GetVirtualMemoryLimit()
418 MEMORYSTATUSEX memStatus;
419 if (::GlobalMemoryStatusEx(&memStatus))
421 return (size_t)memStatus.ullAvailVirtual;
427 // Get the physical memory that this process can use.
429 // non zero if it has succeeded, 0 if it has failed
431 // If a process runs with a restricted memory limit, it returns the limit. If there's no limit
432 // specified, it returns amount of actual physical memory.
433 uint64_t GCToOSInterface::GetPhysicalMemoryLimit()
435 size_t restricted_limit = GetRestrictedPhysicalMemoryLimit();
436 if (restricted_limit != 0)
437 return restricted_limit;
439 MEMORYSTATUSEX memStatus;
440 if (::GlobalMemoryStatusEx(&memStatus))
442 return memStatus.ullTotalPhys;
450 // memory_load - A number between 0 and 100 that specifies the approximate percentage of physical memory
451 // that is in use (0 indicates no memory use and 100 indicates full memory use).
452 // available_physical - The amount of physical memory currently available, in bytes.
453 // available_page_file - The maximum amount of memory the current process can commit, in bytes.
454 void GCToOSInterface::GetMemoryStatus(uint32_t* memory_load, uint64_t* available_physical, uint64_t* available_page_file)
456 uint64_t restricted_limit = GetRestrictedPhysicalMemoryLimit();
457 if (restricted_limit != 0)
459 PROCESS_MEMORY_COUNTERS pmc;
460 if (GCGetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc)))
463 *memory_load = (uint32_t)((float)pmc.WorkingSetSize * 100.0 / (float)restricted_limit);
464 if (available_physical)
465 *available_physical = restricted_limit - pmc.WorkingSetSize;
466 // Available page file doesn't mean much when physical memory is restricted since
467 // we don't know how much of it is available to this process so we are not going to
468 // bother to make another OS call for it.
469 if (available_page_file)
470 *available_page_file = 0;
477 ::GetProcessMemoryLoad(&ms);
479 if (memory_load != nullptr)
480 *memory_load = ms.dwMemoryLoad;
481 if (available_physical != nullptr)
482 *available_physical = ms.ullAvailPhys;
483 if (available_page_file != nullptr)
484 *available_page_file = ms.ullAvailPageFile;
487 // Get a high precision performance counter
490 int64_t GCToOSInterface::QueryPerformanceCounter()
493 if (!::QueryPerformanceCounter(&ts))
495 assert(false && "Failed to query performance counter");
501 // Get a frequency of the high precision performance counter
503 // The counter frequency
504 int64_t GCToOSInterface::QueryPerformanceFrequency()
507 if (!::QueryPerformanceFrequency(&ts))
509 assert(false && "Failed to query performance counter");
515 // Get a time stamp with a low precision
517 // Time stamp in milliseconds
518 uint32_t GCToOSInterface::GetLowPrecisionTimeStamp()
520 return ::GetTickCount();
523 // Parameters of the GC thread stub
524 struct GCThreadStubParam
526 GCThreadFunction GCThreadFunction;
530 // GC thread stub to convert GC thread function to an OS specific thread function
531 static DWORD GCThreadStub(void* param)
533 GCThreadStubParam *stubParam = (GCThreadStubParam*)param;
534 GCThreadFunction function = stubParam->GCThreadFunction;
535 void* threadParam = stubParam->GCThreadParam;
539 function(threadParam);
545 // Create a new thread for GC use
547 // function - the function to be executed by the thread
548 // param - parameters of the thread
549 // affinity - processor affinity of the thread
551 // true if it has succeeded, false if it has failed
552 bool GCToOSInterface::CreateThread(GCThreadFunction function, void* param, GCThreadAffinity* affinity)
556 std::unique_ptr<GCThreadStubParam> stubParam(new (std::nothrow) GCThreadStubParam());
562 stubParam->GCThreadFunction = function;
563 stubParam->GCThreadParam = param;
565 HANDLE gc_thread = ::CreateThread(
567 512 * 1024 /* Thread::StackSize_Medium */,
568 (LPTHREAD_START_ROUTINE)GCThreadStub,
570 CREATE_SUSPENDED | STACK_SIZE_PARAM_IS_A_RESERVATION,
579 bool result = !!::SetThreadPriority(gc_thread, /* THREAD_PRIORITY_ABOVE_NORMAL );*/ THREAD_PRIORITY_HIGHEST );
580 assert(result && "failed to set thread priority");
582 if (affinity->Group != GCThreadAffinity::None)
584 assert(affinity->Processor != GCThreadAffinity::None);
586 ga.Group = (WORD)affinity->Group;
587 ga.Reserved[0] = 0; // reserve must be filled with zero
588 ga.Reserved[1] = 0; // otherwise call may fail
590 ga.Mask = (size_t)1 << affinity->Processor;
592 bool result = !!::SetThreadGroupAffinity(gc_thread, &ga, nullptr);
593 assert(result && "failed to set thread affinity");
595 else if (affinity->Processor != GCThreadAffinity::None)
597 ::SetThreadAffinityMask(gc_thread, (DWORD_PTR)1 << affinity->Processor);
600 ResumeThread(gc_thread);
601 CloseHandle(gc_thread);
606 // Initialize the critical section
607 void CLRCriticalSection::Initialize()
609 ::InitializeCriticalSection(&m_cs);
612 // Destroy the critical section
613 void CLRCriticalSection::Destroy()
615 ::DeleteCriticalSection(&m_cs);
618 // Enter the critical section. Blocks until the section can be entered.
619 void CLRCriticalSection::Enter()
621 ::EnterCriticalSection(&m_cs);
624 // Leave the critical section
625 void CLRCriticalSection::Leave()
627 ::LeaveCriticalSection(&m_cs);
630 // WindowsEvent is an implementation of GCEvent that forwards
631 // directly to Win32 APIs.
638 Impl() : m_hEvent(INVALID_HANDLE_VALUE) {}
642 return m_hEvent != INVALID_HANDLE_VALUE;
648 BOOL result = SetEvent(m_hEvent);
649 assert(result && "SetEvent failed");
655 BOOL result = ResetEvent(m_hEvent);
656 assert(result && "ResetEvent failed");
659 uint32_t Wait(uint32_t timeout, bool alertable)
661 UNREFERENCED_PARAMETER(alertable);
664 return WaitForSingleObject(m_hEvent, timeout);
670 BOOL result = CloseHandle(m_hEvent);
671 assert(result && "CloseHandle failed");
672 m_hEvent = INVALID_HANDLE_VALUE;
675 bool CreateAutoEvent(bool initialState)
677 m_hEvent = CreateEvent(nullptr, false, initialState, nullptr);
681 bool CreateManualEvent(bool initialState)
683 m_hEvent = CreateEvent(nullptr, true, initialState, nullptr);
693 void GCEvent::CloseEvent()
695 assert(m_impl != nullptr);
696 m_impl->CloseEvent();
701 assert(m_impl != nullptr);
705 void GCEvent::Reset()
707 assert(m_impl != nullptr);
711 uint32_t GCEvent::Wait(uint32_t timeout, bool alertable)
713 assert(m_impl != nullptr);
714 return m_impl->Wait(timeout, alertable);
717 bool GCEvent::CreateAutoEventNoThrow(bool initialState)
719 // [DESKTOP TODO] The difference between events and OS events is
720 // whether or not the hosting API is made aware of them. When (if)
721 // we implement hosting support for Local GC, we will need to be
722 // aware of the host here.
723 return CreateOSAutoEventNoThrow(initialState);
726 bool GCEvent::CreateManualEventNoThrow(bool initialState)
728 // [DESKTOP TODO] The difference between events and OS events is
729 // whether or not the hosting API is made aware of them. When (if)
730 // we implement hosting support for Local GC, we will need to be
731 // aware of the host here.
732 return CreateOSManualEventNoThrow(initialState);
735 bool GCEvent::CreateOSAutoEventNoThrow(bool initialState)
737 assert(m_impl == nullptr);
738 std::unique_ptr<GCEvent::Impl> event(new (std::nothrow) GCEvent::Impl());
744 if (!event->CreateAutoEvent(initialState))
749 m_impl = event.release();
753 bool GCEvent::CreateOSManualEventNoThrow(bool initialState)
755 assert(m_impl == nullptr);
756 std::unique_ptr<GCEvent::Impl> event(new (std::nothrow) GCEvent::Impl());
762 if (!event->CreateManualEvent(initialState))
767 m_impl = event.release();