From 4b11dc566a5bbfa1378d6266525c281b028abcc8 Mon Sep 17 00:00:00 2001 From: Jiyoung Yun Date: Fri, 10 Feb 2017 20:35:12 +0900 Subject: Imported Upstream version 1.0.0.9910 --- src/gc/CMakeLists.txt | 10 +- src/gc/env/gcenv.base.h | 21 +- src/gc/env/gcenv.ee.h | 2 + src/gc/gc.cpp | 168 ++++------- src/gc/gc.h | 8 +- src/gc/gccommon.cpp | 3 +- src/gc/gcenv.ee.standalone.inl | 88 ++++-- src/gc/gcenv.unix.cpp | 308 ------------------- src/gc/gcenv.windows.cpp | 625 -------------------------------------- src/gc/gcimpl.h | 1 - src/gc/gcinterface.ee.h | 5 + src/gc/gcinterface.h | 23 +- src/gc/gcpriv.h | 4 +- src/gc/gcsvr.cpp | 1 + src/gc/gcwks.cpp | 1 + src/gc/sample/CMakeLists.txt | 2 +- src/gc/sample/GCSample.cpp | 13 +- src/gc/sample/GCSample.vcxproj | 2 +- src/gc/sample/gcenv.ee.cpp | 9 +- src/gc/softwarewritewatch.cpp | 11 +- src/gc/softwarewritewatch.h | 40 +-- src/gc/unix/CMakeLists.txt | 10 + src/gc/unix/config.h.in | 14 + src/gc/unix/configure.cmake | 40 +++ src/gc/unix/gcenv.unix.cpp | 627 +++++++++++++++++++++++++++++++++++++++ src/gc/windows/gcenv.windows.cpp | 625 ++++++++++++++++++++++++++++++++++++++ 26 files changed, 1501 insertions(+), 1160 deletions(-) delete mode 100644 src/gc/gcenv.unix.cpp delete mode 100644 src/gc/gcenv.windows.cpp create mode 100644 src/gc/unix/CMakeLists.txt create mode 100644 src/gc/unix/config.h.in create mode 100644 src/gc/unix/configure.cmake create mode 100644 src/gc/unix/gcenv.unix.cpp create mode 100644 src/gc/windows/gcenv.windows.cpp (limited to 'src/gc') 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/env/gcenv.base.h b/src/gc/env/gcenv.base.h index 94f73762f8..9fe583f9a6 100644 --- a/src/gc/env/gcenv.base.h +++ b/src/gc/env/gcenv.base.h @@ -96,7 +96,7 @@ inline HRESULT HRESULT_FROM_WIN32(unsigned long x) #define UNREFERENCED_PARAMETER(P) (void)(P) #ifdef PLATFORM_UNIX -#define _vsnprintf vsnprintf +#define _vsnprintf_s(string, sizeInBytes, count, format, args) vsnprintf(string, sizeInBytes, format, args) #define sprintf_s snprintf #define swprintf_s swprintf #endif @@ -441,8 +441,6 @@ extern MethodTable * g_pFreeObjectMethodTable; extern int32_t g_TrapReturningThreads; -extern bool g_fFinalizerRunOnShutDown; - // // Locks // @@ -454,21 +452,6 @@ Thread * GetThread(); typedef void (CALLBACK *HANDLESCANPROC)(PTR_UNCHECKED_OBJECTREF pref, uintptr_t *pExtraInfo, uintptr_t param1, uintptr_t param2); -class FinalizerThread -{ -public: - static bool Initialize(); - static void EnableFinalization(); - - static bool HaveExtraWorkForFinalizer(); - - static bool IsCurrentThreadFinalizer(); - static void Wait(DWORD timeout, bool allowReentrantWait = false); - static void SignalFinalizationDone(bool fFinalizer); - static void SetFinalizerThread(Thread * pThread); - static HANDLE GetFinalizerEvent(); -}; - bool IsGCSpecialThread(); inline bool dbgOnly_IsSpecialEEThread() @@ -509,8 +492,6 @@ void LogSpewAlways(const char *fmt, ...); // ----------------------------------------------------------------------------------------------------------- -void StompWriteBarrierEphemeral(bool isRuntimeSuspended); -void StompWriteBarrierResize(bool isRuntimeSuspended, bool bReqUpperBoundsCheck); bool IsGCThread(); class CLRConfig diff --git a/src/gc/env/gcenv.ee.h b/src/gc/env/gcenv.ee.h index beb0c1a98f..9f7f266a89 100644 --- a/src/gc/env/gcenv.ee.h +++ b/src/gc/env/gcenv.ee.h @@ -66,6 +66,8 @@ public: static void DiagWalkLOHSurvivors(void* gcContext); static void DiagWalkBGCSurvivors(void* gcContext); static void StompWriteBarrier(WriteBarrierParameters* args); + + static void EnableFinalization(bool foundFinalizers); }; #endif // __GCENV_EE_H__ diff --git a/src/gc/gc.cpp b/src/gc/gc.cpp index 6187938ff8..66c8b6afbc 100644 --- a/src/gc/gc.cpp +++ b/src/gc/gc.cpp @@ -389,7 +389,7 @@ void log_va_msg(const char *fmt, va_list args) int pid_len = sprintf_s (&pBuffer[buffer_start], BUFFERSIZE - buffer_start, "[%5d]", (uint32_t)GCToOSInterface::GetCurrentThreadIdForLogging()); buffer_start += pid_len; memset(&pBuffer[buffer_start], '-', BUFFERSIZE - buffer_start); - int msg_len = _vsnprintf(&pBuffer[buffer_start], BUFFERSIZE - buffer_start, fmt, args ); + int msg_len = _vsnprintf_s(&pBuffer[buffer_start], BUFFERSIZE - buffer_start, _TRUNCATE, fmt, args ); if (msg_len == -1) { msg_len = BUFFERSIZE - buffer_start; @@ -1402,9 +1402,6 @@ int mark_time, plan_time, sweep_time, reloc_time, compact_time; #ifndef MULTIPLE_HEAPS -#define ephemeral_low g_gc_ephemeral_low -#define ephemeral_high g_gc_ephemeral_high - #endif // MULTIPLE_HEAPS #ifdef TRACE_GC @@ -2187,27 +2184,22 @@ void stomp_write_barrier_resize(bool is_runtime_suspended, bool requires_upper_b args.card_table = g_gc_card_table; args.lowest_address = g_gc_lowest_address; args.highest_address = g_gc_highest_address; +#ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + if (SoftwareWriteWatch::IsEnabledForGCHeap()) + { + args.write_watch_table = g_gc_sw_ww_table; + } +#endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP GCToEEInterface::StompWriteBarrier(&args); } -void stomp_write_barrier_ephemeral(bool is_runtime_suspended, uint8_t* ephemeral_lo, uint8_t* ephemeral_hi) +void stomp_write_barrier_ephemeral(uint8_t* ephemeral_low, uint8_t* ephemeral_high) { WriteBarrierParameters args = {}; args.operation = WriteBarrierOp::StompEphemeral; - args.is_runtime_suspended = is_runtime_suspended; - args.ephemeral_lo = g_gc_ephemeral_low; - args.ephemeral_hi = g_gc_ephemeral_high; -#ifdef MULTIPLE_HEAPS - // It is not correct to update the EE's g_ephemeral_low and g_ephemeral_high - // to anything other than their default values when using Server GC, since - // there is no single ephemeral generation across all of the heaps. - // Server GC write barriers do not reference these two globals, but ErectWriteBarrier does. - // - // When MULTIPLE_HEAPS is defined, g_gc_ephemeral_low and g_gc_ephemeral_high should - // always have their default values. - assert(args.ephemeral_lo == (uint8_t*)1); - assert(args.ephemeral_hi == (uint8_t*)~0); -#endif // MULTIPLE_HEAPS + args.is_runtime_suspended = true; + args.ephemeral_low = ephemeral_low; + args.ephemeral_high = ephemeral_high; GCToEEInterface::StompWriteBarrier(&args); } @@ -2220,6 +2212,8 @@ void stomp_write_barrier_initialize() args.card_table = g_gc_card_table; args.lowest_address = g_gc_lowest_address; args.highest_address = g_gc_highest_address; + args.ephemeral_low = reinterpret_cast(1); + args.ephemeral_high = reinterpret_cast(~0); GCToEEInterface::StompWriteBarrier(&args); } @@ -2430,6 +2424,10 @@ BOOL gc_heap::ro_segments_in_range; size_t gc_heap::gen0_big_free_spaces = 0; +uint8_t* gc_heap::ephemeral_low; + +uint8_t* gc_heap::ephemeral_high; + uint8_t* gc_heap::lowest_address; uint8_t* gc_heap::highest_address; @@ -7044,6 +7042,7 @@ int gc_heap::grow_brick_card_tables (uint8_t* start, uint8_t* ha = g_gc_highest_address; uint8_t* saved_g_lowest_address = min (start, g_gc_lowest_address); uint8_t* saved_g_highest_address = max (end, g_gc_highest_address); + seg_mapping* new_seg_mapping_table = nullptr; #ifdef BACKGROUND_GC // This value is only for logging purpose - it's not necessarily exactly what we // would commit for mark array but close enough for diagnostics purpose. @@ -7204,14 +7203,18 @@ int gc_heap::grow_brick_card_tables (uint8_t* start, #ifdef GROWABLE_SEG_MAPPING_TABLE { - seg_mapping* new_seg_mapping_table = (seg_mapping*)(mem + st_table_offset_aligned); + new_seg_mapping_table = (seg_mapping*)(mem + st_table_offset_aligned); new_seg_mapping_table = (seg_mapping*)((uint8_t*)new_seg_mapping_table - size_seg_mapping_table_of (0, (align_lower_segment (saved_g_lowest_address)))); memcpy(&new_seg_mapping_table[seg_mapping_word_of(g_gc_lowest_address)], &seg_mapping_table[seg_mapping_word_of(g_gc_lowest_address)], size_seg_mapping_table_of(g_gc_lowest_address, g_gc_highest_address)); - seg_mapping_table = new_seg_mapping_table; + // new_seg_mapping_table gets assigned to seg_mapping_table at the bottom of this function, + // not here. The reason for this is that, if we fail at mark array committing (OOM) and we've + // already switched seg_mapping_table to point to the new mapping table, we'll decommit it and + // run into trouble. By not assigning here, we're making sure that we will not change seg_mapping_table + // if an OOM occurs. } #endif //GROWABLE_SEG_MAPPING_TABLE @@ -7225,7 +7228,7 @@ int gc_heap::grow_brick_card_tables (uint8_t* start, translated_ct = translate_card_table (ct); dprintf (GC_TABLE_LOG, ("card table: %Ix(translated: %Ix), seg map: %Ix, mark array: %Ix", - (size_t)ct, (size_t)translated_ct, (size_t)seg_mapping_table, (size_t)card_table_mark_array (ct))); + (size_t)ct, (size_t)translated_ct, (size_t)new_seg_mapping_table, (size_t)card_table_mark_array (ct))); #ifdef BACKGROUND_GC if (hp->should_commit_mark_array()) @@ -7277,9 +7280,6 @@ int gc_heap::grow_brick_card_tables (uint8_t* start, } g_gc_card_table = translated_ct; - g_gc_lowest_address = saved_g_lowest_address; - g_gc_highest_address = saved_g_highest_address; - SoftwareWriteWatch::SetResizedUntranslatedTable( mem + sw_ww_table_offset, saved_g_lowest_address, @@ -7290,6 +7290,8 @@ int gc_heap::grow_brick_card_tables (uint8_t* start, // grow version of the write barrier. This test tells us if the new // segment was allocated at a lower address than the old, requiring // that we start doing an upper bounds check in the write barrier. + g_gc_lowest_address = saved_g_lowest_address; + g_gc_highest_address = saved_g_highest_address; stomp_write_barrier_resize(true, la != saved_g_lowest_address); write_barrier_updated = true; @@ -7304,6 +7306,9 @@ int gc_heap::grow_brick_card_tables (uint8_t* start, g_gc_card_table = translated_ct; } + seg_mapping_table = new_seg_mapping_table; + + GCToOSInterface::FlushProcessWriteBuffers(); g_gc_lowest_address = saved_g_lowest_address; g_gc_highest_address = saved_g_highest_address; @@ -9662,7 +9667,7 @@ void gc_heap::make_generation (generation& gen, heap_segment* seg, uint8_t* star #endif //FREE_USAGE_STATS } -void gc_heap::adjust_ephemeral_limits (bool is_runtime_suspended) +void gc_heap::adjust_ephemeral_limits () { ephemeral_low = generation_allocation_start (generation_of (max_generation - 1)); ephemeral_high = heap_segment_reserved (ephemeral_heap_segment); @@ -9670,8 +9675,10 @@ void gc_heap::adjust_ephemeral_limits (bool is_runtime_suspended) dprintf (3, ("new ephemeral low: %Ix new ephemeral high: %Ix", (size_t)ephemeral_low, (size_t)ephemeral_high)) +#ifndef MULTIPLE_HEAPS // This updates the write barrier helpers with the new info. - stomp_write_barrier_ephemeral(is_runtime_suspended, ephemeral_low, ephemeral_high); + stomp_write_barrier_ephemeral(ephemeral_low, ephemeral_high); +#endif // MULTIPLE_HEAPS } #if defined(TRACE_GC) || defined(GC_CONFIG_DRIVEN) @@ -10466,7 +10473,7 @@ gc_heap::init_gc_heap (int h_number) make_background_mark_stack (b_arr); #endif //BACKGROUND_GC - adjust_ephemeral_limits(true); + adjust_ephemeral_limits(); #ifdef MARK_ARRAY // why would we clear the mark array for this page? it should be cleared.. @@ -15364,7 +15371,11 @@ void gc_heap::gc1() if (!settings.concurrent) #endif //BACKGROUND_GC { - adjust_ephemeral_limits(!!IsGCThread()); +#ifndef FEATURE_REDHAWK + // IsGCThread() always returns false on CoreRT, but this assert is useful in CoreCLR. + assert(!!IsGCThread()); +#endif // FEATURE_REDHAWK + adjust_ephemeral_limits(); } #ifdef BACKGROUND_GC @@ -16204,7 +16215,11 @@ BOOL gc_heap::expand_soh_with_minimal_gc() dd_gc_new_allocation (dynamic_data_of (max_generation)) -= ephemeral_size; dd_new_allocation (dynamic_data_of (max_generation)) = dd_gc_new_allocation (dynamic_data_of (max_generation)); - adjust_ephemeral_limits(!!IsGCThread()); +#ifndef FEATURE_REDHAWK + // IsGCThread() always returns false on CoreRT, but this assert is useful in CoreCLR. + assert(!!IsGCThread()); +#endif // FEATURE_REDHAWK + adjust_ephemeral_limits(); return TRUE; } else @@ -32778,8 +32793,8 @@ gc_heap::verify_heap (BOOL begin_gc_p) #endif //BACKGROUND_GC #ifndef MULTIPLE_HEAPS - if ((g_gc_ephemeral_low != generation_allocation_start (generation_of (max_generation - 1))) || - (g_gc_ephemeral_high != heap_segment_reserved (ephemeral_heap_segment))) + if ((ephemeral_low != generation_allocation_start (generation_of (max_generation - 1))) || + (ephemeral_high != heap_segment_reserved (ephemeral_heap_segment))) { FATAL_GC_ERROR(); } @@ -35140,11 +35155,7 @@ GCHeap::GarbageCollectGeneration (unsigned int gen, gc_reason reason) #endif //!MULTIPLE_HEAPS #ifdef FEATURE_PREMORTEM_FINALIZATION - if ((!pGenGCHeap->settings.concurrent && pGenGCHeap->settings.found_finalizers) || - FinalizerThread::HaveExtraWorkForFinalizer()) - { - FinalizerThread::EnableFinalization(); - } + GCToEEInterface::EnableFinalization(!pGenGCHeap->settings.concurrent && pGenGCHeap->settings.found_finalizers); #endif // FEATURE_PREMORTEM_FINALIZATION return dd_collection_count (dd); @@ -35681,85 +35692,6 @@ void GCHeap::SetFinalizationRun (Object* obj) #endif // FEATURE_PREMORTEM_FINALIZATION -//---------------------------------------------------------------------------- -// -// Write Barrier Support for bulk copy ("Clone") operations -// -// StartPoint is the target bulk copy start point -// len is the length of the bulk copy (in bytes) -// -// -// Performance Note: -// -// This is implemented somewhat "conservatively", that is we -// assume that all the contents of the bulk copy are object -// references. If they are not, and the value lies in the -// ephemeral range, we will set false positives in the card table. -// -// We could use the pointer maps and do this more accurately if necessary - -#if defined(_MSC_VER) && defined(_TARGET_X86_) -#pragma optimize("y", on) // Small critical routines, don't put in EBP frame -#endif //_MSC_VER && _TARGET_X86_ - -void -GCHeap::SetCardsAfterBulkCopy( Object **StartPoint, size_t len ) -{ - Object **rover; - Object **end; - - // Target should aligned - assert(Aligned ((size_t)StartPoint)); - - - // Don't optimize the Generation 0 case if we are checking for write barrier voilations - // since we need to update the shadow heap even in the generation 0 case. -#if defined (WRITE_BARRIER_CHECK) && !defined (SERVER_GC) - if (g_pConfig->GetHeapVerifyLevel() & EEConfig::HEAPVERIFY_BARRIERCHECK) - for(unsigned i=0; i < len / sizeof(Object*); i++) - updateGCShadow(&StartPoint[i], StartPoint[i]); -#endif //WRITE_BARRIER_CHECK && !SERVER_GC - -#ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP - if (SoftwareWriteWatch::IsEnabledForGCHeap()) - { - SoftwareWriteWatch::SetDirtyRegion(StartPoint, len); - } -#endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP - - // If destination is in Gen 0 don't bother - if ( -#ifdef BACKGROUND_GC - (!gc_heap::settings.concurrent) && -#endif //BACKGROUND_GC - (g_theGCHeap->WhichGeneration( (Object*) StartPoint ) == 0)) - return; - - rover = StartPoint; - end = StartPoint + (len/sizeof(Object*)); - while (rover < end) - { - if ( (((uint8_t*)*rover) >= g_gc_ephemeral_low) && (((uint8_t*)*rover) < g_gc_ephemeral_high) ) - { - // Set Bit For Card and advance to next card - size_t card = gcard_of ((uint8_t*)rover); - - Interlocked::Or (&g_gc_card_table[card/card_word_width], (1U << (card % card_word_width))); - // Skip to next card for the object - rover = (Object**)align_on_card ((uint8_t*)(rover+1)); - } - else - { - rover++; - } - } -} - -#if defined(_MSC_VER) && defined(_TARGET_X86_) -#pragma optimize("", on) // Go back to command line default optimizations -#endif //_MSC_VER && _TARGET_X86_ - - #ifdef FEATURE_PREMORTEM_FINALIZATION //-------------------------------------------------------------------- @@ -36304,7 +36236,7 @@ CFinalize::ScanForFinalization (promote_func* pfn, int gen, BOOL mark_only_p, if (hp->settings.concurrent && hp->settings.found_finalizers) { if (!mark_only_p) - FinalizerThread::EnableFinalization(); + GCToEEInterface::EnableFinalization(true); } } @@ -36560,11 +36492,13 @@ void GCHeap::DiagScanFinalizeQueue (fq_scan_fn fn, ScanContext* sc) void GCHeap::DiagScanHandles (handle_scan_fn fn, int gen_number, ScanContext* context) { + UNREFERENCED_PARAMETER(gen_number); GCScan::GcScanHandlesForProfilerAndETW (max_generation, context, fn); } void GCHeap::DiagScanDependentHandles (handle_scan_fn fn, int gen_number, ScanContext* context) { + UNREFERENCED_PARAMETER(gen_number); GCScan::GcScanDependentHandlesForProfilerAndETW (max_generation, context, fn); } diff --git a/src/gc/gc.h b/src/gc/gc.h index b7f1e956b6..7332e42885 100644 --- a/src/gc/gc.h +++ b/src/gc/gc.h @@ -140,8 +140,7 @@ class DacHeapWalker; extern "C" uint32_t* g_gc_card_table; extern "C" uint8_t* g_gc_lowest_address; extern "C" uint8_t* g_gc_highest_address; -extern "C" uint8_t* g_gc_ephemeral_low; -extern "C" uint8_t* g_gc_ephemeral_high; +extern "C" bool g_fFinalizerRunOnShutDown; namespace WKS { ::IGCHeapInternal* CreateGCHeap(); @@ -270,6 +269,11 @@ public: return mt->GetBaseSize() >= LARGE_OBJECT_SIZE; } + void SetFinalizeRunOnShutdown(bool value) + { + g_fFinalizerRunOnShutDown = value; + } + protected: public: #if defined(FEATURE_BASICFREEZE) && defined(VERIFY_HEAP) diff --git a/src/gc/gccommon.cpp b/src/gc/gccommon.cpp index d1ccddd205..133f05e490 100644 --- a/src/gc/gccommon.cpp +++ b/src/gc/gccommon.cpp @@ -41,8 +41,7 @@ uint8_t* g_shadow_lowest_address = NULL; uint32_t* g_gc_card_table; uint8_t* g_gc_lowest_address = 0; uint8_t* g_gc_highest_address = 0; -uint8_t* g_gc_ephemeral_low = (uint8_t*)1; -uint8_t* g_gc_ephemeral_high = (uint8_t*)~0; +bool g_fFinalizerRunOnShutDown = false; VOLATILE(int32_t) m_GCLock = -1; diff --git a/src/gc/gcenv.ee.standalone.inl b/src/gc/gcenv.ee.standalone.inl index 3b64586d70..31f3d1d8da 100644 --- a/src/gc/gcenv.ee.standalone.inl +++ b/src/gc/gcenv.ee.standalone.inl @@ -11,166 +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); } +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/gcenv.windows.cpp b/src/gc/gcenv.windows.cpp deleted file mode 100644 index a636478245..0000000000 --- a/src/gc/gcenv.windows.cpp +++ /dev/null @@ -1,625 +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 -#include -#include -#include -#include "windows.h" -#include "psapi.h" -#include "env/gcenv.structs.h" -#include "env/gcenv.base.h" -#include "env/gcenv.os.h" - -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; - } -} - -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 hinstKernel32 = 0; - - PIS_PROCESS_IN_JOB GCIsProcessInJob = 0; - PQUERY_INFORMATION_JOB_OBJECT GCQueryInformationJobObject = 0; - - hinstKernel32 = LoadLibraryEx(L"kernel32.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); - if (!hinstKernel32) - goto exit; - - GCIsProcessInJob = (PIS_PROCESS_IN_JOB)GetProcAddress(hinstKernel32, "IsProcessInJob"); - if (!GCIsProcessInJob) - goto exit; - - if (!GCIsProcessInJob(GetCurrentProcess(), NULL, &in_job_p)) - goto exit; - - if (in_job_p) - { - GCGetProcessMemoryInfo = (PGET_PROCESS_MEMORY_INFO)GetProcAddress(hinstKernel32, "K32GetProcessMemoryInfo"); - - if (!GCGetProcessMemoryInfo) - goto exit; - - GCQueryInformationJobObject = (PQUERY_INFORMATION_JOB_OBJECT)GetProcAddress(hinstKernel32, "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 (job_physical_memory_limit == (size_t)UINTPTR_MAX) - { - job_physical_memory_limit = 0; - - FreeLibrary(hinstKernel32); - } - - 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 (size_t)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/gcimpl.h b/src/gc/gcimpl.h index 7e3a13a743..cb91c4dc3e 100644 --- a/src/gc/gcimpl.h +++ b/src/gc/gcimpl.h @@ -198,7 +198,6 @@ public: BOOL FinalizeAppDomain(AppDomain *pDomain, BOOL fRunFinalizers); BOOL ShouldRestartFinalizerWatchDog(); - void SetCardsAfterBulkCopy( Object**, size_t); void DiagWalkObject (Object* obj, walk_fn fn, void* context); public: // FIX diff --git a/src/gc/gcinterface.ee.h b/src/gc/gcinterface.ee.h index c5f87ef031..7c0eea2d95 100644 --- a/src/gc/gcinterface.ee.h +++ b/src/gc/gcinterface.ee.h @@ -128,6 +128,11 @@ public: // barrier if it needs to be updated. virtual void StompWriteBarrier(WriteBarrierParameters* args) = 0; + + // Signals to the finalizer thread that there are objects ready to + // be finalized. + virtual + void EnableFinalization(bool foundFinalizers) = 0; }; #endif // _GCINTERFACE_EE_H_ diff --git a/src/gc/gcinterface.h b/src/gc/gcinterface.h index 1457848992..99d79df633 100644 --- a/src/gc/gcinterface.h +++ b/src/gc/gcinterface.h @@ -46,7 +46,9 @@ enum class WriteBarrierOp { StompResize, StompEphemeral, - Initialize + Initialize, + SwitchToWriteWatch, + SwitchToNonWriteWatch }; // Arguments to GCToEEInterface::StompWriteBarrier @@ -85,11 +87,15 @@ struct WriteBarrierParameters // The new start of the ephemeral generation. // Used for WriteBarrierOp::StompEphemeral. - uint8_t* ephemeral_lo; + uint8_t* ephemeral_low; // The new end of the ephemeral generation. // Used for WriteBarrierOp::StompEphemeral. - uint8_t* ephemeral_hi; + uint8_t* ephemeral_high; + + // The new write watch table, if we are using our own write watch + // implementation. Used for WriteBarrierOp::SwitchToWriteWatch only. + uint8_t* write_watch_table; }; #include "gcinterface.ee.h" @@ -148,6 +154,10 @@ struct segment_info #define max_generation 2 +// The bit shift used to convert a memory address into an index into the +// Software Write Watch table. +#define SOFTWARE_WRITE_WATCH_AddressToTableByteIndexShift 0xc + class Object; class IGCHeap; @@ -303,6 +313,10 @@ public: // Gets the next finalizable object. virtual Object* GetNextFinalizable() = 0; + // Sets whether or not the GC should report all finalizable objects as + // ready to be finalized, instead of only collectable objects. + virtual void SetFinalizeRunOnShutdown(bool value) = 0; + /* =========================================================================== BCL routines. These are routines that are directly exposed by mscorlib @@ -398,9 +412,6 @@ public: // sanity checks asserting that a GC has not occured. virtual unsigned GetGcCount() = 0; - // Sets cards after an object has been memmoved. - virtual void SetCardsAfterBulkCopy(Object** obj, size_t length) = 0; - // Gets whether or not the home heap of this alloc context matches the heap // associated with this thread. virtual bool IsThreadUsingAllocationContextHeap(gc_alloc_context* acontext, int thread_number) = 0; diff --git a/src/gc/gcpriv.h b/src/gc/gcpriv.h index 3bed8c2cf8..1f97d7f2d5 100644 --- a/src/gc/gcpriv.h +++ b/src/gc/gcpriv.h @@ -1671,7 +1671,7 @@ protected: PER_HEAP void reset_write_watch (BOOL concurrent_p); PER_HEAP - void adjust_ephemeral_limits (bool is_runtime_suspended); + void adjust_ephemeral_limits (); PER_HEAP void make_generation (generation& gen, heap_segment* seg, uint8_t* start, uint8_t* pointer); @@ -2802,13 +2802,11 @@ public: PER_HEAP void exit_gc_done_event_lock(); -#ifdef MULTIPLE_HEAPS PER_HEAP uint8_t* ephemeral_low; //lowest ephemeral address PER_HEAP uint8_t* ephemeral_high; //highest ephemeral address -#endif //MULTIPLE_HEAPS PER_HEAP uint32_t* card_table; diff --git a/src/gc/gcsvr.cpp b/src/gc/gcsvr.cpp index cf5fc9335f..70801dd4ee 100644 --- a/src/gc/gcsvr.cpp +++ b/src/gc/gcsvr.cpp @@ -13,6 +13,7 @@ #include "gc.h" #include "gcscan.h" #include "gcdesc.h" +#include "softwarewritewatch.h" #define SERVER_GC 1 diff --git a/src/gc/gcwks.cpp b/src/gc/gcwks.cpp index 574df8215a..5c489df0e0 100644 --- a/src/gc/gcwks.cpp +++ b/src/gc/gcwks.cpp @@ -11,6 +11,7 @@ #include "gc.h" #include "gcscan.h" #include "gcdesc.h" +#include "softwarewritewatch.h" #ifdef SERVER_GC #undef SERVER_GC 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.cpp b/src/gc/sample/GCSample.cpp index 664dc38e94..112d291420 100644 --- a/src/gc/sample/GCSample.cpp +++ b/src/gc/sample/GCSample.cpp @@ -94,14 +94,11 @@ inline void ErectWriteBarrier(Object ** dst, Object * ref) if (((uint8_t*)dst < g_gc_lowest_address) || ((uint8_t*)dst >= g_gc_highest_address)) return; - if((uint8_t*)ref >= g_gc_ephemeral_low && (uint8_t*)ref < g_gc_ephemeral_high) - { - // volatile is used here to prevent fetch of g_card_table from being reordered - // with g_lowest/highest_address check above. See comment in code:gc_heap::grow_brick_card_tables. - uint8_t* pCardByte = (uint8_t *)*(volatile uint8_t **)(&g_gc_card_table) + card_byte((uint8_t *)dst); - if(*pCardByte != 0xFF) - *pCardByte = 0xFF; - } + // volatile is used here to prevent fetch of g_card_table from being reordered + // with g_lowest/highest_address check above. See comment in code:gc_heap::grow_brick_card_tables. + uint8_t* pCardByte = (uint8_t *)*(volatile uint8_t **)(&g_gc_card_table) + card_byte((uint8_t *)dst); + if(*pCardByte != 0xFF) + *pCardByte = 0xFF; } void WriteBarrier(Object ** dst, Object * ref) 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 @@ - + NotUsing diff --git a/src/gc/sample/gcenv.ee.cpp b/src/gc/sample/gcenv.ee.cpp index ac227b4823..e95a78dc48 100644 --- a/src/gc/sample/gcenv.ee.cpp +++ b/src/gc/sample/gcenv.ee.cpp @@ -13,8 +13,6 @@ MethodTable * g_pFreeObjectMethodTable; int32_t g_TrapReturningThreads; -bool g_fFinalizerRunOnShutDown; - EEConfig * g_pConfig; bool CLREventStatic::CreateManualEventNoThrow(bool bInitialState) @@ -259,17 +257,12 @@ void GCToEEInterface::StompWriteBarrier(WriteBarrierParameters* args) { } -void FinalizerThread::EnableFinalization() +void GCToEEInterface::EnableFinalization(bool foundFinalizers) { // Signal to finalizer thread that there are objects to finalize // TODO: Implement for finalization } -bool FinalizerThread::HaveExtraWorkForFinalizer() -{ - return false; -} - bool IsGCSpecialThread() { // TODO: Implement for background GC diff --git a/src/gc/softwarewritewatch.cpp b/src/gc/softwarewritewatch.cpp index fa14a04897..b85293857a 100644 --- a/src/gc/softwarewritewatch.cpp +++ b/src/gc/softwarewritewatch.cpp @@ -3,10 +3,9 @@ // See the LICENSE file in the project root for more information. #include "common.h" -#include "softwarewritewatch.h" - #include "gcenv.h" #include "env/gcenv.os.h" +#include "softwarewritewatch.h" #ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP #ifndef DACCESS_COMPILE @@ -15,8 +14,8 @@ static_assert((static_cast(1) << SOFTWARE_WRITE_WATCH_AddressToTableByte extern "C" { - uint8_t *g_sw_ww_table = nullptr; - bool g_sw_ww_enabled_for_gc_heap = false; + uint8_t *g_gc_sw_ww_table = nullptr; + bool g_gc_sw_ww_enabled_for_gc_heap = false; } void SoftwareWriteWatch::StaticClose() @@ -26,8 +25,8 @@ void SoftwareWriteWatch::StaticClose() return; } - g_sw_ww_enabled_for_gc_heap = false; - g_sw_ww_table = nullptr; + g_gc_sw_ww_enabled_for_gc_heap = false; + g_gc_sw_ww_table = nullptr; } bool SoftwareWriteWatch::GetDirtyFromBlock( diff --git a/src/gc/softwarewritewatch.h b/src/gc/softwarewritewatch.h index 3c8491cecb..0e6e6c8191 100644 --- a/src/gc/softwarewritewatch.h +++ b/src/gc/softwarewritewatch.h @@ -5,25 +5,20 @@ #ifndef __SOFTWARE_WRITE_WATCH_H__ #define __SOFTWARE_WRITE_WATCH_H__ +#include "gcinterface.h" +#include "gc.h" + #ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP #ifndef DACCESS_COMPILE -extern void SwitchToWriteWatchBarrier(bool isRuntimeSuspended); -extern void SwitchToNonWriteWatchBarrier(bool isRuntimeSuspended); - -#define SOFTWARE_WRITE_WATCH_AddressToTableByteIndexShift 0xc - extern "C" { // Table containing the dirty state. This table is translated to exclude the lowest address it represents, see // TranslateTableToExcludeHeapStartAddress. - extern uint8_t *g_sw_ww_table; + extern uint8_t *g_gc_sw_ww_table; // Write watch may be disabled when it is not needed (between GCs for instance). This indicates whether it is enabled. - extern bool g_sw_ww_enabled_for_gc_heap; - - extern uint8_t *g_lowest_address; // start address of the GC heap - extern uint8_t *g_highest_address; // end address of the GC heap + extern bool g_gc_sw_ww_enabled_for_gc_heap; } class SoftwareWriteWatch @@ -116,7 +111,7 @@ inline void SoftwareWriteWatch::VerifyMemoryRegion( inline uint8_t *SoftwareWriteWatch::GetTable() { - return g_sw_ww_table; + return g_gc_sw_ww_table; } inline uint8_t *SoftwareWriteWatch::GetUntranslatedTable() @@ -163,7 +158,7 @@ inline void SoftwareWriteWatch::SetUntranslatedTable(uint8_t *untranslatedTable, assert(ALIGN_DOWN(untranslatedTable, sizeof(size_t)) == untranslatedTable); assert(heapStartAddress != nullptr); - g_sw_ww_table = TranslateTableToExcludeHeapStartAddress(untranslatedTable, heapStartAddress); + g_gc_sw_ww_table = TranslateTableToExcludeHeapStartAddress(untranslatedTable, heapStartAddress); } inline void SoftwareWriteWatch::SetResizedUntranslatedTable( @@ -194,7 +189,7 @@ inline void SoftwareWriteWatch::SetResizedUntranslatedTable( inline bool SoftwareWriteWatch::IsEnabledForGCHeap() { - return g_sw_ww_enabled_for_gc_heap; + return g_gc_sw_ww_enabled_for_gc_heap; } inline void SoftwareWriteWatch::EnableForGCHeap() @@ -204,9 +199,13 @@ inline void SoftwareWriteWatch::EnableForGCHeap() VerifyCreated(); assert(!IsEnabledForGCHeap()); + g_gc_sw_ww_enabled_for_gc_heap = true; - g_sw_ww_enabled_for_gc_heap = true; - SwitchToWriteWatchBarrier(true); + WriteBarrierParameters args = {}; + args.operation = WriteBarrierOp::SwitchToWriteWatch; + args.write_watch_table = g_gc_sw_ww_table; + args.is_runtime_suspended = true; + GCToEEInterface::StompWriteBarrier(&args); } inline void SoftwareWriteWatch::DisableForGCHeap() @@ -216,19 +215,22 @@ inline void SoftwareWriteWatch::DisableForGCHeap() VerifyCreated(); assert(IsEnabledForGCHeap()); + g_gc_sw_ww_enabled_for_gc_heap = false; - g_sw_ww_enabled_for_gc_heap = false; - SwitchToNonWriteWatchBarrier(true); + WriteBarrierParameters args = {}; + args.operation = WriteBarrierOp::SwitchToNonWriteWatch; + args.is_runtime_suspended = true; + GCToEEInterface::StompWriteBarrier(&args); } inline void *SoftwareWriteWatch::GetHeapStartAddress() { - return g_lowest_address; + return g_gc_lowest_address; } inline void *SoftwareWriteWatch::GetHeapEndAddress() { - return g_highest_address; + return g_gc_highest_address; } inline size_t SoftwareWriteWatch::GetTableByteIndex(void *address) 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 + #include + + int main() + { + uint64_t tid; + pthread_threadid_np(pthread_self(), &tid); + return (int)tid; + } + " HAVE_PTHREAD_THREADID_NP) + +check_cxx_source_compiles(" + #include + #include + + int main() + { + return (int)pthread_getthreadid_np(); + } + " HAVE_PTHREAD_GETTHREADID_NP) + +check_cxx_source_runs(" + #include + + 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 +#include +#include +#include + +// 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 +#include +#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 +#else + #error "sys/time.h required by GC PAL for the time being" +#endif // HAVE_SYS_TIME_ + +#ifdef HAVE_SYS_MMAN_H + #include +#else + #error "sys/mman.h required by GC PAL" +#endif // HAVE_SYS_MMAN_H + +#ifdef __linux__ + #include +#endif // __linux__ + +#include // nanosleep +#include // sched_yield +#include +#include // 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 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/windows/gcenv.windows.cpp b/src/gc/windows/gcenv.windows.cpp new file mode 100644 index 0000000000..a636478245 --- /dev/null +++ b/src/gc/windows/gcenv.windows.cpp @@ -0,0 +1,625 @@ +// 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" + +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; + } +} + +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 hinstKernel32 = 0; + + PIS_PROCESS_IN_JOB GCIsProcessInJob = 0; + PQUERY_INFORMATION_JOB_OBJECT GCQueryInformationJobObject = 0; + + hinstKernel32 = LoadLibraryEx(L"kernel32.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); + if (!hinstKernel32) + goto exit; + + GCIsProcessInJob = (PIS_PROCESS_IN_JOB)GetProcAddress(hinstKernel32, "IsProcessInJob"); + if (!GCIsProcessInJob) + goto exit; + + if (!GCIsProcessInJob(GetCurrentProcess(), NULL, &in_job_p)) + goto exit; + + if (in_job_p) + { + GCGetProcessMemoryInfo = (PGET_PROCESS_MEMORY_INFO)GetProcAddress(hinstKernel32, "K32GetProcessMemoryInfo"); + + if (!GCGetProcessMemoryInfo) + goto exit; + + GCQueryInformationJobObject = (PQUERY_INFORMATION_JOB_OBJECT)GetProcAddress(hinstKernel32, "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 (job_physical_memory_limit == (size_t)UINTPTR_MAX) + { + job_physical_memory_limit = 0; + + FreeLibrary(hinstKernel32); + } + + 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 (size_t)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); +} -- cgit v1.2.3