diff options
author | Sean Gillespie <sean@swgillespie.me> | 2017-01-23 19:44:10 -0800 |
---|---|---|
committer | Jan Kotas <jkotas@microsoft.com> | 2017-01-23 19:44:10 -0800 |
commit | bbd32c484f2d00a566217e52bddb6960c2ec0b22 (patch) | |
tree | 2e993c5409feea679563ac18fd5f7600e1412642 | |
parent | d8b995b42714f4e83e0ee35a8ff481143cd62994 (diff) | |
download | coreclr-bbd32c484f2d00a566217e52bddb6960c2ec0b22.tar.gz coreclr-bbd32c484f2d00a566217e52bddb6960c2ec0b22.tar.bz2 coreclr-bbd32c484f2d00a566217e52bddb6960c2ec0b22.zip |
[Local GC] Provide an implementation of GCToOSInterface for Unix-like platforms (#8976)
* Add way to build with FEATURE_STANDALONE_GC from build.sh
* Make CMake changes to build the GC 'PAL' as its own build target (to avoid -nostdinc).
In addition, introduce a "GC PAL" that provides an implementation of
GCToOSInterface on Unix-like platforms, for use with
FEATURE_STANDALONE_GC.
-rw-r--r-- | CMakeLists.txt | 8 | ||||
-rw-r--r-- | build.cmd | 1 | ||||
-rwxr-xr-x | build.sh | 5 | ||||
-rw-r--r-- | clrdefinitions.cmake | 4 | ||||
-rw-r--r-- | src/dlls/mscoree/coreclr/CMakeLists.txt | 6 | ||||
-rw-r--r-- | src/gc/CMakeLists.txt | 10 | ||||
-rw-r--r-- | src/gc/gcenv.ee.standalone.inl | 84 | ||||
-rw-r--r-- | src/gc/gcenv.unix.cpp | 308 | ||||
-rw-r--r-- | src/gc/sample/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/gc/sample/GCSample.vcxproj | 2 | ||||
-rw-r--r-- | src/gc/unix/CMakeLists.txt | 10 | ||||
-rw-r--r-- | src/gc/unix/config.h.in | 14 | ||||
-rw-r--r-- | src/gc/unix/configure.cmake | 40 | ||||
-rw-r--r-- | src/gc/unix/gcenv.unix.cpp | 627 | ||||
-rw-r--r-- | src/gc/windows/gcenv.windows.cpp (renamed from src/gc/gcenv.windows.cpp) | 0 |
15 files changed, 772 insertions, 349 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 2c39780e29..cdf157e186 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -579,6 +579,14 @@ endif(WIN32) include_directories("src/pal/prebuilt/inc") include_directories("bin/obj") +if(FEATURE_STANDALONE_GC) + add_definitions(-DFEATURE_STANDALONE_GC) + + if(CLR_CMAKE_PLATFORM_UNIX) + add_subdirectory(src/gc/unix) + endif(CLR_CMAKE_PLATFORM_UNIX) +endif(FEATURE_STANDALONE_GC) + if (CLR_CMAKE_PLATFORM_UNIX) include_directories("src/pal/inc") include_directories("src/pal/inc/rt") @@ -587,6 +587,7 @@ echo skipmscorlib: skip building System.Private.CoreLib ^(default: System.Privat echo skipnative: skip building native components ^(default: native components are built^). echo skiptests: skip building tests ^(default: tests are built^). echo skipbuildpackages: skip building nuget packages ^(default: packages are built^). +echo buildstandalonegc: builds the GC in a standalone mode. echo -skiprestore: skip restoring packages ^(default: packages are restored during build^). echo -disableoss: Disable Open Source Signing for System.Private.CoreLib. echo -priority=^<N^> : specify a set of test that will be built and run, with priority N. @@ -47,6 +47,7 @@ usage() echo "skipgenerateversion - disable version generation even if MSBuild is supported." echo "cmakeargs - user-settable additional arguments passed to CMake." echo "bindir - output directory (defaults to $__ProjectRoot/bin)" + echo "buildstandalonegc - builds the GC in a standalone mode. Can't be used with \"cmakeargs\"." exit 1 } @@ -724,7 +725,9 @@ while :; do exit 1 fi ;; - + buildstandalonegc) + __cmakeargs="-DFEATURE_STANDALONE_GC=1" + ;; *) __UnprocessedBuildArgs="$__UnprocessedBuildArgs $1" ;; diff --git a/clrdefinitions.cmake b/clrdefinitions.cmake index 7719450ae5..a2b920c194 100644 --- a/clrdefinitions.cmake +++ b/clrdefinitions.cmake @@ -168,10 +168,6 @@ add_definitions(-DFEATURE_RANDOMIZED_STRING_HASHING) add_definitions(-DFEATURE_READYTORUN) set(FEATURE_READYTORUN 1) -if (FEATURE_STANDALONE_GC) - add_definitions(-DFEATURE_STANDALONE_GC) -endif(FEATURE_STANDALONE_GC) - if (CLR_CMAKE_TARGET_ARCH_AMD64 OR CLR_CMAKE_TARGET_ARCH_I386) add_definitions(-DFEATURE_REJIT) endif(CLR_CMAKE_TARGET_ARCH_AMD64 OR CLR_CMAKE_TARGET_ARCH_I386) diff --git a/src/dlls/mscoree/coreclr/CMakeLists.txt b/src/dlls/mscoree/coreclr/CMakeLists.txt index aa7bb0d9b9..afd18d6c27 100644 --- a/src/dlls/mscoree/coreclr/CMakeLists.txt +++ b/src/dlls/mscoree/coreclr/CMakeLists.txt @@ -125,6 +125,12 @@ else() ) endif(WIN32) +if(CLR_CMAKE_PLATFORM_UNIX AND FEATURE_STANDALONE_GC) + list(APPEND CORECLR_LIBRARIES + gc_unix + ) +endif(CLR_CMAKE_PLATFORM_UNIX AND FEATURE_STANDALONE_GC) + if(CLR_CMAKE_PLATFORM_UNIX AND FEATURE_EVENT_TRACE) list(APPEND CORECLR_LIBRARIES eventprovider diff --git a/src/gc/CMakeLists.txt b/src/gc/CMakeLists.txt index d32d1c2dfb..cba1aa9778 100644 --- a/src/gc/CMakeLists.txt +++ b/src/gc/CMakeLists.txt @@ -39,15 +39,11 @@ set( GC_SOURCES_DAC ${GC_SOURCES_DAC_AND_WKS_COMMON}) if(FEATURE_STANDALONE_GC) - if(CLR_CMAKE_PLATFORM_UNIX) + if(NOT 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) + windows/gcenv.windows.cpp) + endif(NOT CLR_CMAKE_PLATFORM_UNIX) endif(FEATURE_STANDALONE_GC) convert_to_absolute_path(GC_SOURCES_WKS ${GC_SOURCES_WKS}) diff --git a/src/gc/gcenv.ee.standalone.inl b/src/gc/gcenv.ee.standalone.inl index e285394eb3..31f3d1d8da 100644 --- a/src/gc/gcenv.ee.standalone.inl +++ b/src/gc/gcenv.ee.standalone.inl @@ -11,172 +11,202 @@ // will be fowarded to this interface instance. extern IGCToCLR* g_theGCToCLR; +// A note about this: +// In general, we don't want to pretend to be smarter than the compiler +// and force it to inline things. However, inlining is here is required +// for correctness as it stands today (though it will not always be required). +// +// The reason for this is because: +// 1) This file (and the GCToEEInterface class) define symbols that are inline +// and static, so the symbol GCToEEInterface::XYZ defines a symbol with weak +// linkage when the function is not inlined, +// 2) src/vm/gcenv.ee.cpp all define symbols that are not inline and instance methods +// of GCToEEInterface, with external linkage. +// 3) When it comes time to link the GC and the VM, the linker observes the duplicate +// symbols and discards the one with weak linkage. +// 4) All of the calls within the GC to the functions in this file are replaced by +// the linker to calls to the implementation of a pure virtual IGCToCLR. The +// functions implementing IGCToCLR have an extra argument (this). +// 5) Now, all calls to these functions from within the GC are doomed because of the +// functions that actually get called expect this to be in rdi, where the compiler +// has placed the first argument instead. +// +// For now, by forcing the compiler to inline these functions, the compiler won't actually +// emit symbols for them and we'll avoid the linker havoc. +#ifdef _MSC_VER + #define ALWAYS_INLINE __forceinline +#else + #define ALWAYS_INLINE __attribute__((always_inline)) inline +#endif + // When we are building the GC in a standalone environment, we // will be dispatching virtually against g_theGCToCLR to call // into the EE. This class provides an identical API to the existing // GCToEEInterface, but only forwards the call onto the global // g_theGCToCLR instance. -inline void GCToEEInterface::SuspendEE(SUSPEND_REASON reason) +ALWAYS_INLINE void GCToEEInterface::SuspendEE(SUSPEND_REASON reason) { assert(g_theGCToCLR != nullptr); g_theGCToCLR->SuspendEE(reason); } -inline void GCToEEInterface::RestartEE(bool bFinishedGC) +ALWAYS_INLINE void GCToEEInterface::RestartEE(bool bFinishedGC) { assert(g_theGCToCLR != nullptr); g_theGCToCLR->RestartEE(bFinishedGC); } -inline void GCToEEInterface::GcScanRoots(promote_func* fn, int condemned, int max_gen, ScanContext* sc) +ALWAYS_INLINE void GCToEEInterface::GcScanRoots(promote_func* fn, int condemned, int max_gen, ScanContext* sc) { assert(g_theGCToCLR != nullptr); g_theGCToCLR->GcScanRoots(fn, condemned, max_gen, sc); } -inline void GCToEEInterface::GcStartWork(int condemned, int max_gen) +ALWAYS_INLINE void GCToEEInterface::GcStartWork(int condemned, int max_gen) { assert(g_theGCToCLR != nullptr); g_theGCToCLR->GcStartWork(condemned, max_gen); } -inline void GCToEEInterface::AfterGcScanRoots(int condemned, int max_gen, ScanContext* sc) +ALWAYS_INLINE void GCToEEInterface::AfterGcScanRoots(int condemned, int max_gen, ScanContext* sc) { assert(g_theGCToCLR != nullptr); g_theGCToCLR->AfterGcScanRoots(condemned, max_gen, sc); } -inline void GCToEEInterface::GcBeforeBGCSweepWork() +ALWAYS_INLINE void GCToEEInterface::GcBeforeBGCSweepWork() { assert(g_theGCToCLR != nullptr); g_theGCToCLR->GcBeforeBGCSweepWork(); } -inline void GCToEEInterface::GcDone(int condemned) +ALWAYS_INLINE void GCToEEInterface::GcDone(int condemned) { assert(g_theGCToCLR != nullptr); g_theGCToCLR->GcDone(condemned); } -inline bool GCToEEInterface::RefCountedHandleCallbacks(Object * pObject) +ALWAYS_INLINE bool GCToEEInterface::RefCountedHandleCallbacks(Object * pObject) { assert(g_theGCToCLR != nullptr); return g_theGCToCLR->RefCountedHandleCallbacks(pObject); } -inline void GCToEEInterface::SyncBlockCacheWeakPtrScan(HANDLESCANPROC scanProc, uintptr_t lp1, uintptr_t lp2) +ALWAYS_INLINE void GCToEEInterface::SyncBlockCacheWeakPtrScan(HANDLESCANPROC scanProc, uintptr_t lp1, uintptr_t lp2) { assert(g_theGCToCLR != nullptr); g_theGCToCLR->SyncBlockCacheWeakPtrScan(scanProc, lp1, lp2); } -inline void GCToEEInterface::SyncBlockCacheDemote(int max_gen) +ALWAYS_INLINE void GCToEEInterface::SyncBlockCacheDemote(int max_gen) { assert(g_theGCToCLR != nullptr); g_theGCToCLR->SyncBlockCacheDemote(max_gen); } -inline void GCToEEInterface::SyncBlockCachePromotionsGranted(int max_gen) +ALWAYS_INLINE void GCToEEInterface::SyncBlockCachePromotionsGranted(int max_gen) { assert(g_theGCToCLR != nullptr); g_theGCToCLR->SyncBlockCachePromotionsGranted(max_gen); } -inline bool GCToEEInterface::IsPreemptiveGCDisabled(Thread * pThread) +ALWAYS_INLINE bool GCToEEInterface::IsPreemptiveGCDisabled(Thread * pThread) { assert(g_theGCToCLR != nullptr); return g_theGCToCLR->IsPreemptiveGCDisabled(pThread); } -inline void GCToEEInterface::EnablePreemptiveGC(Thread * pThread) +ALWAYS_INLINE void GCToEEInterface::EnablePreemptiveGC(Thread * pThread) { assert(g_theGCToCLR != nullptr); g_theGCToCLR->EnablePreemptiveGC(pThread); } -inline void GCToEEInterface::DisablePreemptiveGC(Thread * pThread) +ALWAYS_INLINE void GCToEEInterface::DisablePreemptiveGC(Thread * pThread) { assert(g_theGCToCLR != nullptr); g_theGCToCLR->DisablePreemptiveGC(pThread); } -inline gc_alloc_context * GCToEEInterface::GetAllocContext(Thread * pThread) +ALWAYS_INLINE gc_alloc_context * GCToEEInterface::GetAllocContext(Thread * pThread) { assert(g_theGCToCLR != nullptr); return g_theGCToCLR->GetAllocContext(pThread); } -inline bool GCToEEInterface::CatchAtSafePoint(Thread * pThread) +ALWAYS_INLINE bool GCToEEInterface::CatchAtSafePoint(Thread * pThread) { assert(g_theGCToCLR != nullptr); return g_theGCToCLR->CatchAtSafePoint(pThread); } -inline void GCToEEInterface::GcEnumAllocContexts(enum_alloc_context_func* fn, void* param) +ALWAYS_INLINE void GCToEEInterface::GcEnumAllocContexts(enum_alloc_context_func* fn, void* param) { assert(g_theGCToCLR != nullptr); g_theGCToCLR->GcEnumAllocContexts(fn, param); } -inline Thread* GCToEEInterface::CreateBackgroundThread(GCBackgroundThreadFunction threadStart, void* arg) +ALWAYS_INLINE Thread* GCToEEInterface::CreateBackgroundThread(GCBackgroundThreadFunction threadStart, void* arg) { assert(g_theGCToCLR != nullptr); return g_theGCToCLR->CreateBackgroundThread(threadStart, arg); } -inline void GCToEEInterface::DiagGCStart(int gen, bool isInduced) +ALWAYS_INLINE void GCToEEInterface::DiagGCStart(int gen, bool isInduced) { assert(g_theGCToCLR != nullptr); g_theGCToCLR->DiagGCStart(gen, isInduced); } -inline void GCToEEInterface::DiagUpdateGenerationBounds() +ALWAYS_INLINE void GCToEEInterface::DiagUpdateGenerationBounds() { assert(g_theGCToCLR != nullptr); g_theGCToCLR->DiagUpdateGenerationBounds(); } -inline void GCToEEInterface::DiagGCEnd(size_t index, int gen, int reason, bool fConcurrent) +ALWAYS_INLINE void GCToEEInterface::DiagGCEnd(size_t index, int gen, int reason, bool fConcurrent) { assert(g_theGCToCLR != nullptr); g_theGCToCLR->DiagGCEnd(index, gen, reason, fConcurrent); } -inline void GCToEEInterface::DiagWalkFReachableObjects(void* gcContext) +ALWAYS_INLINE void GCToEEInterface::DiagWalkFReachableObjects(void* gcContext) { assert(g_theGCToCLR != nullptr); g_theGCToCLR->DiagWalkFReachableObjects(gcContext); } -inline void GCToEEInterface::DiagWalkSurvivors(void* gcContext) +ALWAYS_INLINE void GCToEEInterface::DiagWalkSurvivors(void* gcContext) { assert(g_theGCToCLR != nullptr); g_theGCToCLR->DiagWalkSurvivors(gcContext); } -inline void GCToEEInterface::DiagWalkLOHSurvivors(void* gcContext) +ALWAYS_INLINE void GCToEEInterface::DiagWalkLOHSurvivors(void* gcContext) { assert(g_theGCToCLR != nullptr); g_theGCToCLR->DiagWalkLOHSurvivors(gcContext); } -inline void GCToEEInterface::DiagWalkBGCSurvivors(void* gcContext) +ALWAYS_INLINE void GCToEEInterface::DiagWalkBGCSurvivors(void* gcContext) { assert(g_theGCToCLR != nullptr); return g_theGCToCLR->DiagWalkBGCSurvivors(gcContext); } -inline void GCToEEInterface::StompWriteBarrier(WriteBarrierParameters* args) +ALWAYS_INLINE void GCToEEInterface::StompWriteBarrier(WriteBarrierParameters* args) { assert(g_theGCToCLR != nullptr); g_theGCToCLR->StompWriteBarrier(args); } -inline void GCToEEInterface::EnableFinalization(bool foundFinalizers) +ALWAYS_INLINE void GCToEEInterface::EnableFinalization(bool foundFinalizers) { assert(g_theGCToCLR != nullptr); g_theGCToCLR->EnableFinalization(foundFinalizers); } +#undef ALWAYS_INLINE + #endif // __GCTOENV_EE_STANDALONE_INL__ diff --git a/src/gc/gcenv.unix.cpp b/src/gc/gcenv.unix.cpp deleted file mode 100644 index 0235952e28..0000000000 --- a/src/gc/gcenv.unix.cpp +++ /dev/null @@ -1,308 +0,0 @@ -// 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" - -// 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/sample/CMakeLists.txt b/src/gc/sample/CMakeLists.txt index 9552cc51e2..29fd32f2ff 100644 --- a/src/gc/sample/CMakeLists.txt +++ b/src/gc/sample/CMakeLists.txt @@ -22,7 +22,7 @@ set(SOURCES if(WIN32) list(APPEND SOURCES - ../gcenv.windows.cpp) + ../windows/gcenv.windows.cpp) add_definitions(-DUNICODE=1) else() list(APPEND SOURCES diff --git a/src/gc/sample/GCSample.vcxproj b/src/gc/sample/GCSample.vcxproj index 1716f462ee..105e289c1a 100644 --- a/src/gc/sample/GCSample.vcxproj +++ b/src/gc/sample/GCSample.vcxproj @@ -87,7 +87,7 @@ <ClCompile Include="GCSample.cpp" /> <ClCompile Include="..\gccommon.cpp" /> <ClCompile Include="..\gceewks.cpp" /> - <ClCompile Include="..\gcenv.windows.cpp"> + <ClCompile Include="..\windows\gcenv.windows.cpp"> <PrecompiledHeader>NotUsing</PrecompiledHeader> </ClCompile> <ClCompile Include="..\gcscan.cpp" /> diff --git a/src/gc/unix/CMakeLists.txt b/src/gc/unix/CMakeLists.txt new file mode 100644 index 0000000000..ef66abf32a --- /dev/null +++ b/src/gc/unix/CMakeLists.txt @@ -0,0 +1,10 @@ +set(CMAKE_INCLUDE_CURRENT_DIR ON) +add_compile_options(-fPIC) +include_directories("../env") + +include(configure.cmake) + +set(GC_PAL_SOURCES + gcenv.unix.cpp) + +add_library(gc_unix STATIC ${GC_PAL_SOURCES} ${VERSION_FILE_PATH}) diff --git a/src/gc/unix/config.h.in b/src/gc/unix/config.h.in new file mode 100644 index 0000000000..7578c74c05 --- /dev/null +++ b/src/gc/unix/config.h.in @@ -0,0 +1,14 @@ +// 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. + +#ifndef __CONFIG_H__ +#define __CONFIG_H__ + +#cmakedefine01 HAVE_SYS_TIME_H +#cmakedefine01 HAVE_SYS_MMAN_H +#cmakedefine01 HAVE_PTHREAD_THREADID_NP +#cmakedefine01 HAVE_PTHREAD_GETTHREADID_NP +#cmakedefine01 HAVE_SCHED_GETCPU + +#endif // __CONFIG_H__
\ No newline at end of file diff --git a/src/gc/unix/configure.cmake b/src/gc/unix/configure.cmake new file mode 100644 index 0000000000..6e1e8fe27d --- /dev/null +++ b/src/gc/unix/configure.cmake @@ -0,0 +1,40 @@ +check_include_files(sys/time.h HAVE_SYS_TIME_H) +check_include_files(sys/mman.h HAVE_SYS_MMAN_H) +check_cxx_source_compiles(" + #include <pthread.h> + #include <stdint.h> + + int main() + { + uint64_t tid; + pthread_threadid_np(pthread_self(), &tid); + return (int)tid; + } + " HAVE_PTHREAD_THREADID_NP) + +check_cxx_source_compiles(" + #include <pthread.h> + #include <stdint.h> + + int main() + { + return (int)pthread_getthreadid_np(); + } + " HAVE_PTHREAD_GETTHREADID_NP) + +check_cxx_source_runs(" + #include <sched.h> + + int main() + { + int result = sched_getcpu(); + if (result == -1) + { + return 1; + } + + return 0; + } + " HAVE_SCHED_GETCPU) + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h)
\ No newline at end of file diff --git a/src/gc/unix/gcenv.unix.cpp b/src/gc/unix/gcenv.unix.cpp new file mode 100644 index 0000000000..34a45b3cc1 --- /dev/null +++ b/src/gc/unix/gcenv.unix.cpp @@ -0,0 +1,627 @@ +// 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 <cstddef> +#include <cassert> +#include <memory> + +// The CoreCLR PAL defines _POSIX_C_SOURCE to avoid calling non-posix pthread functions. +// This isn't something we want, because we're totally fine using non-posix functions. +#if defined(__APPLE__) + #define _DARWIN_C_SOURCE +#endif // definfed(__APPLE__) + +#include <pthread.h> +#include <signal.h> +#include "config.h" + +// clang typedefs uint64_t to be unsigned long long, which clashes with +// PAL/MSVC's unsigned long, causing linker errors. This ugly hack +// will go away once the GC doesn't depend on PAL headers. +typedef unsigned long uint64_t_hack; +#define uint64_t uint64_t_hack +static_assert(sizeof(uint64_t) == 8, "unsigned long isn't 8 bytes"); + +#ifndef __out_z +#define __out_z +#endif // __out_z + +#include "gcenv.structs.h" +#include "gcenv.base.h" +#include "gcenv.os.h" + +#ifndef FEATURE_STANDALONE_GC + #error "A GC-private implementation of GCToOSInterface should only be used with FEATURE_STANDALONE_GC" +#endif // FEATURE_STANDALONE_GC + +#ifdef HAVE_SYS_TIME_H + #include <sys/time.h> +#else + #error "sys/time.h required by GC PAL for the time being" +#endif // HAVE_SYS_TIME_ + +#ifdef HAVE_SYS_MMAN_H + #include <sys/mman.h> +#else + #error "sys/mman.h required by GC PAL" +#endif // HAVE_SYS_MMAN_H + +#ifdef __linux__ + #include <sys/syscall.h> +#endif // __linux__ + +#include <time.h> // nanosleep +#include <sched.h> // sched_yield +#include <errno.h> +#include <unistd.h> // sysconf + +// The number of milliseconds in a second. +static const int tccSecondsToMilliSeconds = 1000; + +// The number of microseconds in a second. +static const int tccSecondsToMicroSeconds = 1000000; + +// The number of microseconds in a millisecond. +static const int tccMilliSecondsToMicroSeconds = 1000; + +// The number of nanoseconds in a millisecond. +static const int tccMilliSecondsToNanoSeconds = 1000000; + +// The cachced number of logical CPUs observed. +static uint32_t g_logicalCpuCount = 0; + +// Helper memory page used by the FlushProcessWriteBuffers +static uint8_t g_helperPage[OS_PAGE_SIZE] __attribute__((aligned(OS_PAGE_SIZE))); + +// Mutex to make the FlushProcessWriteBuffersMutex thread safe +static pthread_mutex_t g_flushProcessWriteBuffersMutex; + +// Initialize the interface implementation +// Return: +// true if it has succeeded, false if it has failed +bool GCToOSInterface::Initialize() +{ + // Calculate and cache the number of processors on this machine + int cpuCount = sysconf(_SC_NPROCESSORS_ONLN); + if (cpuCount == -1) + { + return false; + } + + g_logicalCpuCount = cpuCount; + + // Verify that the s_helperPage is really aligned to the g_SystemInfo.dwPageSize + assert((((size_t)g_helperPage) & (OS_PAGE_SIZE - 1)) == 0); + + // Locking the page ensures that it stays in memory during the two mprotect + // calls in the FlushProcessWriteBuffers below. If the page was unmapped between + // those calls, they would not have the expected effect of generating IPI. + int status = mlock(g_helperPage, OS_PAGE_SIZE); + + if (status != 0) + { + return false; + } + + status = pthread_mutex_init(&g_flushProcessWriteBuffersMutex, NULL); + if (status != 0) + { + munlock(g_helperPage, OS_PAGE_SIZE); + return false; + } + + return true; +} + +// Shutdown the interface implementation +void GCToOSInterface::Shutdown() +{ + int ret = munlock(g_helperPage, OS_PAGE_SIZE); + assert(ret == 0); + ret = pthread_mutex_destroy(&g_flushProcessWriteBuffersMutex); + assert(ret == 0); +} + +// 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, as best we can retrieve it. +uint64_t GCToOSInterface::GetCurrentThreadIdForLogging() +{ +#if defined(__linux__) + return (uint64_t)syscall(SYS_gettid); +#elif HAVE_PTHREAD_GETTHREADID_NP + return (uint64_t)pthread_getthreadid_np(); +#elif HAVE_PTHREAD_THREADID_NP + unsigned long long tid; + pthread_threadid_np(pthread_self(), &tid); + return (uint64_t)tid; +#else + // Fallback in case we don't know how to get integer thread id on the current platform + return (uint64_t)pthread_self(); +#endif +} + +// Get the process ID of the process. +uint32_t GCToOSInterface::GetCurrentProcessId() +{ + return getpid(); +} + +// 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) +{ + // TODO(segilles) + return false; +} + +// Get the number of the current processor +uint32_t GCToOSInterface::GetCurrentProcessorNumber() +{ +#if HAVE_SCHED_GETCPU + int processorNumber = sched_getcpu(); + assert(processorNumber != -1); + return processorNumber; +#else + return 0; +#endif +} + +// Check if the OS supports getting current processor number +bool GCToOSInterface::CanGetCurrentProcessorNumber() +{ + return HAVE_SCHED_GETCPU; +} + +// Flush write buffers of processors that are executing threads of the current process +void GCToOSInterface::FlushProcessWriteBuffers() +{ + int status = pthread_mutex_lock(&g_flushProcessWriteBuffersMutex); + assert(status == 0 && "Failed to lock the flushProcessWriteBuffersMutex lock"); + + // Changing a helper memory page protection from read / write to no access + // causes the OS to issue IPI to flush TLBs on all processors. This also + // results in flushing the processor buffers. + status = mprotect(g_helperPage, OS_PAGE_SIZE, PROT_READ | PROT_WRITE); + assert(status == 0 && "Failed to change helper page protection to read / write"); + + // Ensure that the page is dirty before we change the protection so that + // we prevent the OS from skipping the global TLB flush. + __sync_add_and_fetch((size_t*)g_helperPage, 1); + + status = mprotect(g_helperPage, OS_PAGE_SIZE, PROT_NONE); + assert(status == 0 && "Failed to change helper page protection to no access"); + + status = pthread_mutex_unlock(&g_flushProcessWriteBuffersMutex); + assert(status == 0 && "Failed to unlock the flushProcessWriteBuffersMutex lock"); +} + +// Break into a debugger. Uses a compiler intrinsic if one is available, +// otherwise raises a SIGTRAP. +void GCToOSInterface::DebugBreak() +{ + // __has_builtin is only defined by clang. GCC doesn't have a debug + // trap intrinsic anyway. +#ifndef __has_builtin + #define __has_builtin(x) 0 +#endif // __has_builtin + +#if __has_builtin(__builtin_debugtrap) + __builtin_debugtrap(); +#else + raise(SIGTRAP); +#endif +} + +// Get number of logical processors +uint32_t GCToOSInterface::GetLogicalCpuCount() +{ + return g_logicalCpuCount; +} + +// 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) +{ + if (sleepMSec == 0) + { + return; + } + + timespec requested; + requested.tv_sec = sleepMSec / tccSecondsToMilliSeconds; + requested.tv_nsec = (sleepMSec - requested.tv_sec * tccSecondsToMilliSeconds) * tccMilliSecondsToNanoSeconds; + + timespec remaining; + while (nanosleep(&requested, &remaining) == EINTR) + { + requested = remaining; + } +} + +// 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) +{ + int ret = sched_yield(); + + // sched_yield never fails on Linux, unclear about other OSes + assert(ret == 0); +} + +// 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) +{ + assert(!(flags & VirtualReserveFlags::WriteWatch) && "WriteWatch not supported on Unix"); + if (alignment == 0) + { + alignment = OS_PAGE_SIZE; + } + + size_t alignedSize = size + (alignment - OS_PAGE_SIZE); + void * pRetVal = mmap(nullptr, alignedSize, PROT_NONE, MAP_ANON | MAP_PRIVATE, -1, 0); + + if (pRetVal != NULL) + { + void * pAlignedRetVal = (void *)(((size_t)pRetVal + (alignment - 1)) & ~(alignment - 1)); + size_t startPadding = (size_t)pAlignedRetVal - (size_t)pRetVal; + if (startPadding != 0) + { + int ret = munmap(pRetVal, startPadding); + assert(ret == 0); + } + + size_t endPadding = alignedSize - (startPadding + size); + if (endPadding != 0) + { + int ret = munmap((void *)((size_t)pAlignedRetVal + size), endPadding); + assert(ret == 0); + } + + pRetVal = pAlignedRetVal; + } + + return pRetVal; +} + +// 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) +{ + int ret = munmap(address, size); + + return (ret == 0); +} + +// 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 mprotect(address, size, PROT_WRITE | PROT_READ) == 0; +} + +// 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 mprotect(address, size, PROT_NONE) == 0; +} + +// 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) +{ + // TODO(CoreCLR#1259) pipe to madvise? + return false; +} + +// Check if the OS supports write watching +bool GCToOSInterface::SupportsWriteWatch() +{ + 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) +{ + assert(!"should never call ResetWriteWatch on Unix"); +} + +// 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) +{ + assert(!"should never call GetWriteWatch on Unix"); + return false; +} + +// 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 + 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) +{ + // TODO(segilles) processor detection + return false; +} + +// Get number of processors assigned to the current process +// Return: +// The number of processors +uint32_t GCToOSInterface::GetCurrentProcessCpuCount() +{ + return g_logicalCpuCount; +} + +// 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() +{ +#ifdef BIT64 + // There is no API to get the total virtual address space size on + // Unix, so we use a constant value representing 128TB, which is + // the approximate size of total user virtual address space on + // the currently supported Unix systems. + static const uint64_t _128TB = (1ull << 47); + return _128TB; +#else + return (size_t)-1; +#endif +} + +// 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() +{ + long pages = sysconf(_SC_PHYS_PAGES); + if (pages == -1) + { + return 0; + } + + long pageSize = sysconf(_SC_PAGE_SIZE); + if (pageSize == -1) + { + return 0; + } + + return pages * pageSize; +} + +// 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) +{ + if (memory_load != nullptr || available_physical != nullptr) + { + uint64_t total = GetPhysicalMemoryLimit(); + + uint64_t available = 0; + uint32_t load = 0; + + // Get the physical memory in use - from it, we can get the physical memory available. + // We do this only when we have the total physical memory available. + if (total > 0) + { + available = sysconf(_SC_PHYS_PAGES) * sysconf(_SC_PAGE_SIZE); + uint64_t used = total - available; + load = (uint32_t)((used * 100) / total); + } + + if (memory_load != nullptr) + *memory_load = load; + if (available_physical != nullptr) + *available_physical = available; + } + + if (available_page_file != nullptr) + *available_page_file = 0; +} + +// Get a high precision performance counter +// Return: +// The counter value +int64_t GCToOSInterface::QueryPerformanceCounter() +{ + // TODO: This is not a particularly efficient implementation - we certainly could + // do much more specific platform-dependent versions if we find that this method + // runs hot. However, most likely it does not. + struct timeval tv; + if (gettimeofday(&tv, NULL) == -1) + { + assert(!"gettimeofday() failed"); + // TODO (segilles) unconditional asserts + return 0; + } + return (int64_t) tv.tv_sec * (int64_t) tccSecondsToMicroSeconds + (int64_t) tv.tv_usec; +} + +// Get a frequency of the high precision performance counter +// Return: +// The counter frequency +int64_t GCToOSInterface::QueryPerformanceFrequency() +{ + // The counter frequency of gettimeofday is in microseconds. + return tccSecondsToMicroSeconds; +} + +// Get a time stamp with a low precision +// Return: +// Time stamp in milliseconds +uint32_t GCToOSInterface::GetLowPrecisionTimeStamp() +{ + // TODO(segilles) this is pretty naive, we can do better + uint64_t retval = 0; + struct timeval tv; + if (gettimeofday(&tv, NULL) == 0) + { + retval = (tv.tv_sec * tccSecondsToMilliSeconds) + (tv.tv_usec / tccMilliSecondsToMicroSeconds); + } + else + { + assert(!"gettimeofday() failed\n"); + } + + return retval; +} + +// 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 void* GCThreadStub(void* param) +{ + GCThreadStubParam *stubParam = (GCThreadStubParam*)param; + GCThreadFunction function = stubParam->GCThreadFunction; + void* threadParam = stubParam->GCThreadParam; + + delete stubParam; + + function(threadParam); + + return NULL; +} + +// 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) +{ + std::unique_ptr<GCThreadStubParam> stubParam(new (std::nothrow) GCThreadStubParam()); + if (!stubParam) + { + return false; + } + + stubParam->GCThreadFunction = function; + stubParam->GCThreadParam = param; + + pthread_attr_t attrs; + + int st = pthread_attr_init(&attrs); + assert(st == 0); + + // Create the thread as detached, that means not joinable + st = pthread_attr_setdetachstate(&attrs, PTHREAD_CREATE_DETACHED); + assert(st == 0); + + pthread_t threadId; + st = pthread_create(&threadId, &attrs, GCThreadStub, stubParam.get()); + + if (st == 0) + { + stubParam.release(); + } + + int st2 = pthread_attr_destroy(&attrs); + assert(st2 == 0); + + return (st == 0); +} + +// Initialize the critical section +void CLRCriticalSection::Initialize() +{ + int st = pthread_mutex_init(&m_cs.mutex, NULL); + assert(st == 0); +} + +// Destroy the critical section +void CLRCriticalSection::Destroy() +{ + int st = pthread_mutex_destroy(&m_cs.mutex); + assert(st == 0); +} + +// Enter the critical section. Blocks until the section can be entered. +void CLRCriticalSection::Enter() +{ + pthread_mutex_lock(&m_cs.mutex); +} + +// Leave the critical section +void CLRCriticalSection::Leave() +{ + pthread_mutex_unlock(&m_cs.mutex); +} diff --git a/src/gc/gcenv.windows.cpp b/src/gc/windows/gcenv.windows.cpp index a636478245..a636478245 100644 --- a/src/gc/gcenv.windows.cpp +++ b/src/gc/windows/gcenv.windows.cpp |