diff options
author | Sean Gillespie <segilles@microsoft.com> | 2017-05-17 15:07:34 -0700 |
---|---|---|
committer | Sean Gillespie <segilles@microsoft.com> | 2017-06-01 10:19:59 -0700 |
commit | 1a183684b1ecf63ece8a2fd80173f083c0deea52 (patch) | |
tree | 907b31b417732768b28e80adfb4d920497f0d3f0 | |
parent | dde63bc1aa39aabae77fb89aad583483965c523e (diff) | |
download | coreclr-1a183684b1ecf63ece8a2fd80173f083c0deea52.tar.gz coreclr-1a183684b1ecf63ece8a2fd80173f083c0deea52.tar.bz2 coreclr-1a183684b1ecf63ece8a2fd80173f083c0deea52.zip |
[Local GC] Scaffolding for loading a standalone GC (#11242)
* Configure the build system to build a CoreCLR capable of loading a standalone GC
* Proto-implementation of dynamic GC loading
* Build the GC with the VM's CMakeLists when doing a non-standalone build of the GC
* [Local GC] Introduce a new feature define, FEATURE_STANDALONE_GC_ONLY, to be used by the CI to explicitly test local GC dynamic loading code paths
* Fix the FEATURE_STANDALONE_GC_ONLY build for unix linkers
* Rebase against master
* Code review feedback: use the existing Unix exports file
-rw-r--r-- | CMakeLists.txt | 4 | ||||
-rw-r--r-- | build.cmd | 8 | ||||
-rwxr-xr-x | build.sh | 2 | ||||
-rw-r--r-- | src/CMakeLists.txt | 4 | ||||
-rw-r--r-- | src/dlls/mscordac/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/dlls/mscoree/coreclr/CMakeLists.txt | 6 | ||||
-rw-r--r-- | src/dlls/mscoree/mscorwks_unixexports.src | 3 | ||||
-rw-r--r-- | src/gc/CMakeLists.txt | 42 | ||||
-rw-r--r-- | src/gc/dac/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/gc/gc.h | 7 | ||||
-rw-r--r-- | src/gc/gccommon.cpp | 31 | ||||
-rw-r--r-- | src/gc/gcenv.ee.standalone.inl | 9 | ||||
-rw-r--r-- | src/gc/gcinterface.ee.h | 4 | ||||
-rw-r--r-- | src/gc/gcinterface.h | 19 | ||||
-rw-r--r-- | src/gc/sample/GCSample.cpp | 2 | ||||
-rw-r--r-- | src/gc/sample/gcenv.h | 6 | ||||
-rw-r--r-- | src/gc/unix/gcenv.unix.cpp | 6 | ||||
-rw-r--r-- | src/gc/wks/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/inc/clrconfigvalues.h | 2 | ||||
-rw-r--r-- | src/vm/CMakeLists.txt | 55 | ||||
-rw-r--r-- | src/vm/ceemain.cpp | 123 | ||||
-rw-r--r-- | src/vm/gcenv.ee.common.cpp | 394 | ||||
-rw-r--r-- | src/vm/gcenv.ee.cpp | 415 | ||||
-rw-r--r-- | src/vm/gcenv.ee.h | 5 | ||||
-rw-r--r-- | src/vm/gcenv.ee.standalone.cpp | 30 | ||||
-rw-r--r-- | src/vm/gcenv.ee.static.cpp | 25 |
26 files changed, 712 insertions, 494 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index f55b54c498..f6e0987ebe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -585,6 +585,10 @@ if(FEATURE_STANDALONE_GC) endif(CLR_CMAKE_PLATFORM_UNIX) endif(FEATURE_STANDALONE_GC) +if(FEATURE_STANDALONE_GC_ONLY) + add_definitions(-DFEATURE_STANDALONE_GC_ONLY) +endif(FEATURE_STANDALONE_GC_ONLY) + if (CLR_CMAKE_PLATFORM_UNIX) include_directories("src/pal/inc") include_directories("src/pal/inc/rt") @@ -76,6 +76,7 @@ set __BuildTypeDebug=0 set __BuildTypeChecked=0 set __BuildTypeRelease=0 set __BuildStandaloneGC="-DFEATURE_STANDALONE_GC=0" +set __BuildStandaloneGCOnly="-DFEATURE_STANDALONE_GC_ONLY=0" set __PgoInstrument=0 set __IbcTuning= @@ -139,7 +140,12 @@ if /i "%1" == "usenmakemakefiles" (set __NMakeMakefiles=1&set __ConfigureOnly= if /i "%1" == "pgoinstrument" (set __PgoInstrument=1&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) if /i "%1" == "ibcinstrument" (set __IbcTuning=/Tuning&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) if /i "%1" == "toolset_dir" (set __ToolsetDir=%2&set __PassThroughArgs=%__PassThroughArgs% %2&set processedArgs=!processedArgs! %1 %2&shift&shift&goto Arg_Loop) -if /i "%1" == "buildstandalonegc" (set __BuildStandaloneGC="-DFEATURE_STANDALONE_GC=1"&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) +if /i "%1" == "buildstandalonegc" ( + set __BuildStandaloneGC="-DFEATURE_STANDALONE_GC=1" + set __BuildStandaloneGCOnly="-DFEATURE_STANDALONE_GC_ONLY=1" + set processedArgs=!processedArgs! %1 + shift&goto Arg_Loop +) @REM The following can be deleted once the CI system that passes it is updated to not pass it. if /i "%1" == "altjitcrossgen" (set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) @@ -800,7 +800,7 @@ while :; do fi ;; buildstandalonegc) - __cmakeargs="-DFEATURE_STANDALONE_GC=1" + __cmakeargs="-DFEATURE_STANDALONE_GC=1 -DFEATURE_STANDALONE_GC_ONLY=1" ;; msbuildonunsupportedplatform) __msbuildonunsupportedplatform=1 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b761b6a82d..9359580468 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -149,7 +149,9 @@ add_subdirectory(utilcode) add_subdirectory(gcinfo) add_subdirectory(coreclr) add_subdirectory(jit) -add_subdirectory(gc) +if(FEATURE_STANDALONE_GC) + add_subdirectory(gc) +endif(FEATURE_STANDALONE_GC) add_subdirectory(vm) add_subdirectory(md) add_subdirectory(debug) diff --git a/src/dlls/mscordac/CMakeLists.txt b/src/dlls/mscordac/CMakeLists.txt index afe5bea7d0..82582f4def 100644 --- a/src/dlls/mscordac/CMakeLists.txt +++ b/src/dlls/mscordac/CMakeLists.txt @@ -89,7 +89,6 @@ set(COREDAC_LIBRARIES strongname_dac utilcode_dac unwinder_dac - gc_dac ${END_LIBRARY_GROUP} # End group of libraries that have circular references ) diff --git a/src/dlls/mscoree/coreclr/CMakeLists.txt b/src/dlls/mscoree/coreclr/CMakeLists.txt index 7a4617fc52..81aaad45f0 100644 --- a/src/dlls/mscoree/coreclr/CMakeLists.txt +++ b/src/dlls/mscoree/coreclr/CMakeLists.txt @@ -73,6 +73,10 @@ if(FEATURE_MERGE_JIT_AND_ENGINE) set(CLRJIT_STATIC clrjit_static) endif(FEATURE_MERGE_JIT_AND_ENGINE) +if(FEATURE_STANDALONE_GC_ONLY) + set(STANDALONE_GC gc_standalone) +endif(FEATURE_STANDALONE_GC_ONLY) + # IMPORTANT! Please do not rearrange the order of the libraries. The linker on Linux is # order dependent and changing the order can result in undefined symbols in the shared # library. @@ -83,7 +87,7 @@ set(CORECLR_LIBRARIES debug-pal ${LIB_UNWINDER} cee_wks - gc_wks + ${STANDALONE_GC} ${END_LIBRARY_GROUP} # End group of libraries that have circular references mdcompiler_wks mdruntime_wks diff --git a/src/dlls/mscoree/mscorwks_unixexports.src b/src/dlls/mscoree/mscorwks_unixexports.src index 271ff8ae7c..73ffc0c1c5 100644 --- a/src/dlls/mscoree/mscorwks_unixexports.src +++ b/src/dlls/mscoree/mscorwks_unixexports.src @@ -104,3 +104,6 @@ GlobalMemoryStatusEx VirtualQuery WideCharToMultiByte WriteFile + +; Function for initializing a standalone GC +InitializeGarbageCollector diff --git a/src/gc/CMakeLists.txt b/src/gc/CMakeLists.txt index 59c18ffd87..f55c1a9a6f 100644 --- a/src/gc/CMakeLists.txt +++ b/src/gc/CMakeLists.txt @@ -1,24 +1,16 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) -include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}) +include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}) include_directories(BEFORE ${CLR_DIR}/src/vm) include_directories(BEFORE ${CLR_DIR}/src/vm/${ARCH_SOURCES_DIR}) +add_definitions(-DBUILD_AS_STANDALONE) + if(CLR_CMAKE_PLATFORM_UNIX) add_compile_options(-fPIC) endif(CLR_CMAKE_PLATFORM_UNIX) -if(CMAKE_CONFIGURATION_TYPES) - foreach (Config DEBUG CHECKED) - set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS $<$<CONFIG:${Config}>:WRITE_BARRIER_CHECK=1>) - endforeach (Config) -else() - if(UPPERCASE_CMAKE_BUILD_TYPE STREQUAL DEBUG OR UPPERCASE_CMAKE_BUILD_TYPE STREQUAL CHECKED) - add_definitions(-DWRITE_BARRIER_CHECK=1) - endif(UPPERCASE_CMAKE_BUILD_TYPE STREQUAL DEBUG OR UPPERCASE_CMAKE_BUILD_TYPE STREQUAL CHECKED) -endif(CMAKE_CONFIGURATION_TYPES) - -set( GC_SOURCES_DAC_AND_WKS_COMMON +set( GC_SOURCES gccommon.cpp gcscan.cpp gcsvr.cpp @@ -27,28 +19,18 @@ set( GC_SOURCES_DAC_AND_WKS_COMMON handletablecore.cpp handletablescan.cpp objecthandle.cpp - softwarewritewatch.cpp) - -set( GC_SOURCES_WKS - ${GC_SOURCES_DAC_AND_WKS_COMMON} + softwarewritewatch.cpp gchandletable.cpp gceesvr.cpp gceewks.cpp handletablecache.cpp) -set( GC_SOURCES_DAC - ${GC_SOURCES_DAC_AND_WKS_COMMON}) - -if(FEATURE_STANDALONE_GC) - if(NOT CLR_CMAKE_PLATFORM_UNIX) - set ( GC_SOURCES_WKS - ${GC_SOURCES_WKS} - windows/gcenv.windows.cpp) - endif(NOT CLR_CMAKE_PLATFORM_UNIX) -endif(FEATURE_STANDALONE_GC) +if(NOT CLR_CMAKE_PLATFORM_UNIX) +set ( GC_SOURCES + ${GC_SOURCES} + windows/gcenv.windows.cpp) +endif(NOT CLR_CMAKE_PLATFORM_UNIX) -convert_to_absolute_path(GC_SOURCES_WKS ${GC_SOURCES_WKS}) -convert_to_absolute_path(GC_SOURCES_DAC ${GC_SOURCES_DAC}) +convert_to_absolute_path(GC_SOURCES ${GC_SOURCES}) -add_subdirectory(wks) -add_subdirectory(dac) +add_library_clr(gc_standalone STATIC ${GC_SOURCES}) diff --git a/src/gc/dac/CMakeLists.txt b/src/gc/dac/CMakeLists.txt deleted file mode 100644 index 1f1c9ebe5c..0000000000 --- a/src/gc/dac/CMakeLists.txt +++ /dev/null @@ -1,2 +0,0 @@ -include(${CLR_DIR}/dac.cmake) -add_library_clr(gc_dac STATIC ${GC_SOURCES_DAC}) diff --git a/src/gc/gc.h b/src/gc/gc.h index 07ae6c916c..2da0cb3966 100644 --- a/src/gc/gc.h +++ b/src/gc/gc.h @@ -27,16 +27,17 @@ Module Name: #include "gcinterface.h" #include "env/gcenv.os.h" -#include "env/gcenv.ee.h" -#ifdef FEATURE_STANDALONE_GC +#ifdef BUILD_AS_STANDALONE #include "gcenv.ee.standalone.inl" // GCStress does not currently work with Standalone GC #ifdef STRESS_HEAP #undef STRESS_HEAP #endif // STRESS_HEAP -#endif // FEATURE_STANDALONE_GC +#else +#include "env/gcenv.ee.h" +#endif // BUILD_AS_STANDALONE /* * Promotion Function Prototypes diff --git a/src/gc/gccommon.cpp b/src/gc/gccommon.cpp index 4950809cda..d7c572260a 100644 --- a/src/gc/gccommon.cpp +++ b/src/gc/gccommon.cpp @@ -17,9 +17,9 @@ IGCHeapInternal* g_theGCHeap; IGCHandleManager* g_theGCHandleManager; -#ifdef FEATURE_STANDALONE_GC +#ifdef BUILD_AS_STANDALONE IGCToCLR* g_theGCToCLR; -#endif // FEATURE_STANDALONE_GC +#endif // BUILD_AS_STANDALONE #ifdef GC_CONFIG_DRIVEN size_t gc_global_mechanisms[MAX_GLOBAL_GC_MECHANISMS_COUNT]; @@ -143,7 +143,30 @@ namespace SVR extern void PopulateDacVars(GcDacVars* dacVars); } -bool InitializeGarbageCollector(IGCToCLR* clrToGC, IGCHeap** gcHeap, IGCHandleManager** gcHandleManager, GcDacVars* gcDacVars) +//------------------------------------------------------------------ +// Externally-facing GC symbols, used to initialize the GC +// ----------------------------------------------------------------- + +#ifdef _MSC_VER +#define DLLEXPORT __declspec(dllexport) +#else +#define DLLEXPORT __attribute__ ((visibility ("default"))) +#endif // _MSC_VER + +#ifdef BUILD_AS_STANDALONE +#define GC_API extern "C" DLLEXPORT +#else +#define GC_API extern "C" +#endif // BUILD_AS_STANDALONE + +GC_API +bool +InitializeGarbageCollector( + /* In */ IGCToCLR* clrToGC, + /* Out */ IGCHeap** gcHeap, + /* Out */ IGCHandleManager** gcHandleManager, + /* Out */ GcDacVars* gcDacVars + ) { LIMITED_METHOD_CONTRACT; @@ -184,7 +207,7 @@ bool InitializeGarbageCollector(IGCToCLR* clrToGC, IGCHeap** gcHeap, IGCHandleMa g_theGCHeap = heap; -#ifdef FEATURE_STANDALONE_GC +#ifdef BUILD_AS_STANDALONE assert(clrToGC != nullptr); g_theGCToCLR = clrToGC; #else diff --git a/src/gc/gcenv.ee.standalone.inl b/src/gc/gcenv.ee.standalone.inl index f6954fc476..31c3455d7b 100644 --- a/src/gc/gcenv.ee.standalone.inl +++ b/src/gc/gcenv.ee.standalone.inl @@ -5,12 +5,17 @@ #ifndef __GCTOENV_EE_STANDALONE_INL__ #define __GCTOENV_EE_STANDALONE_INL__ -#include "env/gcenv.ee.h" +#include "gcinterface.h" // The singular interface instance. All calls in GCToEEInterface // will be fowarded to this interface instance. extern IGCToCLR* g_theGCToCLR; +namespace +{ + +#include "env/gcenv.ee.h" + // 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 @@ -238,4 +243,6 @@ ALWAYS_INLINE MethodTable* GCToEEInterface::GetFreeObjectMethodTable() } #undef ALWAYS_INLINE +} // anonymous namespace + #endif // __GCTOENV_EE_STANDALONE_INL__ diff --git a/src/gc/gcinterface.ee.h b/src/gc/gcinterface.ee.h index 7b868e780e..8290572cd4 100644 --- a/src/gc/gcinterface.ee.h +++ b/src/gc/gcinterface.ee.h @@ -9,9 +9,9 @@ // of the execution engine. Everything that the GC does that requires the EE // to be informed or that requires EE action must go through this interface. // -// When FEATURE_STANDALONE_GC is defined, this class is named IGCToCLR and is +// When BUILD_AS_STANDALONE is defined, this class is named IGCToCLR and is // an abstract class. The EE will provide a class that fulfills this interface, -// and the GC will dispatch virtually on it to call into the EE. When FEATURE_STANDALONE_GC +// and the GC will dispatch virtually on it to call into the EE. When BUILD_AS_STANDALONE // is not defined, this class is named GCToEEInterface and the GC will dispatch statically on it. class IGCToCLR { public: diff --git a/src/gc/gcinterface.h b/src/gc/gcinterface.h index 552a8caec8..425745b0cb 100644 --- a/src/gc/gcinterface.h +++ b/src/gc/gcinterface.h @@ -171,15 +171,24 @@ class Object; class IGCHeap; class IGCHandleManager; -// Initializes the garbage collector. Should only be called -// once, during EE startup. Returns true if the initialization -// was successful, false otherwise. -bool InitializeGarbageCollector(IGCToCLR* clrToGC, IGCHeap** gcHeap, IGCHandleManager** gcHandleTable, GcDacVars* gcDacVars); +// The function that initialzes the garbage collector. +// Should only be called once: here, during EE startup. +// Returns true if the initialization was successful, false otherwise. +typedef bool (*InitializeGarbageCollectorFunction)( + /* In */ IGCToCLR*, + /* Out */ IGCHeap**, + /* Out */ IGCHandleManager**, + /* Out */ GcDacVars* +); + +// The name of the function that initializes the garbage collector, +// to be used as an argument to GetProcAddress. +#define INITIALIZE_GC_FUNCTION_NAME "InitializeGarbageCollector" // The runtime needs to know whether we're using workstation or server GC // long before the GCHeap is created. This function sets the type of // heap that will be created, before InitializeGarbageCollector is called -// and the heap is actually recated. +// and the heap is actually created. void InitializeHeapType(bool bServerHeap); #ifdef WRITE_BARRIER_CHECK diff --git a/src/gc/sample/GCSample.cpp b/src/gc/sample/GCSample.cpp index 0a771b7e91..43cb23878e 100644 --- a/src/gc/sample/GCSample.cpp +++ b/src/gc/sample/GCSample.cpp @@ -107,6 +107,8 @@ void WriteBarrier(Object ** dst, Object * ref) ErectWriteBarrier(dst, ref); } +extern "C" bool InitializeGarbageCollector(IGCToCLR* clrToGC, IGCHeap** gcHeap, IGCHandleManager** gcHandleManager, GcDacVars* gcDacVars); + int __cdecl main(int argc, char* argv[]) { // diff --git a/src/gc/sample/gcenv.h b/src/gc/sample/gcenv.h index 4505f1af30..14f60d8c6e 100644 --- a/src/gc/sample/gcenv.h +++ b/src/gc/sample/gcenv.h @@ -4,9 +4,9 @@ // The sample is to be kept simple, so building the sample // in tandem with a standalone GC is currently not supported. -#ifdef FEATURE_STANDALONE_GC -#undef FEATURE_STANDALONE_GC -#endif // FEATURE_STANDALONE_GC +#ifdef BUILD_AS_STANDALONE +#undef BUILD_AS_STANDALONE +#endif // BUILD_AS_STANDALONE #if defined(_DEBUG) #ifndef _DEBUG_IMPL diff --git a/src/gc/unix/gcenv.unix.cpp b/src/gc/unix/gcenv.unix.cpp index bca0dfedf2..cebc515345 100644 --- a/src/gc/unix/gcenv.unix.cpp +++ b/src/gc/unix/gcenv.unix.cpp @@ -32,9 +32,9 @@ static_assert(sizeof(uint64_t) == 8, "unsigned long isn't 8 bytes"); #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 +#ifndef BUILD_AS_STANDALONE + #error "A GC-private implementation of GCToOSInterface should only be used with BUILD_AS_STANDALONE" +#endif // BUILD_AS_STANDALONE #if HAVE_SYS_TIME_H #include <sys/time.h> diff --git a/src/gc/wks/CMakeLists.txt b/src/gc/wks/CMakeLists.txt deleted file mode 100644 index fcb95a385e..0000000000 --- a/src/gc/wks/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -add_library_clr(gc_wks STATIC ${GC_SOURCES_WKS}) diff --git a/src/inc/clrconfigvalues.h b/src/inc/clrconfigvalues.h index c4722bc44a..81e9fae052 100644 --- a/src/inc/clrconfigvalues.h +++ b/src/inc/clrconfigvalues.h @@ -346,6 +346,8 @@ RETAIL_CONFIG_DWORD_INFO(UNSUPPORTED_GCNumaAware, W("GCNumaAware"), 1, "Specifie RETAIL_CONFIG_DWORD_INFO(EXTERNAL_GCCpuGroup, W("GCCpuGroup"), 0, "Specifies if to enable GC to support CPU groups") RETAIL_CONFIG_DWORD_INFO(UNSUPPORTED_GCHeapCount, W("GCHeapCount"), 0, "") RETAIL_CONFIG_DWORD_INFO(UNSUPPORTED_GCNoAffinitize, W("GCNoAffinitize"), 0, "") +RETAIL_CONFIG_DWORD_INFO(EXTERNAL_GCUseStandalone, W("GCUseStandalone"), 0, "") +RETAIL_CONFIG_STRING_INFO(EXTERNAL_GCStandaloneLocation, W("GCStandaloneLocation"), "") // // IBC diff --git a/src/vm/CMakeLists.txt b/src/vm/CMakeLists.txt index 3895f710b0..60e7ea4c59 100644 --- a/src/vm/CMakeLists.txt +++ b/src/vm/CMakeLists.txt @@ -13,11 +13,15 @@ add_definitions(-D_UNICODE) if(CMAKE_CONFIGURATION_TYPES) # multi-configuration generator? foreach (Config DEBUG CHECKED) - set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS $<$<CONFIG:${Config}>:WRITE_BARRIER_CHECK=1>) + if(NOT FEATURE_STANDALONE_GC_ONLY) + set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS $<$<CONFIG:${Config}>:WRITE_BARRIER_CHECK=1>) + endif(NOT FEATURE_STANDALONE_GC_ONLY) endforeach (Config) else() if(UPPERCASE_CMAKE_BUILD_TYPE STREQUAL DEBUG OR UPPERCASE_CMAKE_BUILD_TYPE STREQUAL CHECKED) - add_definitions(-DWRITE_BARRIER_CHECK=1) + if(NOT FEATURE_STANDALONE_GC_ONLY) + add_definitions(-DWRITE_BARRIER_CHECK=1) + endif(NOT FEATURE_STANDALONE_GC_ONLY) endif(UPPERCASE_CMAKE_BUILD_TYPE STREQUAL DEBUG OR UPPERCASE_CMAKE_BUILD_TYPE STREQUAL CHECKED) endif(CMAKE_CONFIGURATION_TYPES) @@ -117,6 +121,17 @@ set(VM_SOURCES_DAC_AND_WKS_COMMON zapsig.cpp ) +set( GC_SOURCES_DAC_AND_WKS_COMMON + ../gc/gccommon.cpp + ../gc/gcscan.cpp + ../gc/gcsvr.cpp + ../gc/gcwks.cpp + ../gc/handletable.cpp + ../gc/handletablecore.cpp + ../gc/handletablescan.cpp + ../gc/objecthandle.cpp + ../gc/softwarewritewatch.cpp) + if(FEATURE_READYTORUN) list(APPEND VM_SOURCES_DAC_AND_WKS_COMMON readytoruninfo.cpp @@ -129,6 +144,9 @@ set(VM_SOURCES_DAC threaddebugblockinginfo.cpp ) +set(GC_SOURCES_DAC + ${GC_SOURCES_DAC_AND_WKS_COMMON}) + set(VM_SOURCES_WKS ${VM_SOURCES_DAC_AND_WKS_COMMON} appdomainnative.cpp @@ -177,7 +195,9 @@ set(VM_SOURCES_WKS finalizerthread.cpp frameworkexceptionloader.cpp gccover.cpp - gcenv.ee.cpp + gcenv.ee.static.cpp + gcenv.ee.common.cpp + gcenv.os.cpp gchelpers.cpp genmeth.cpp hosting.cpp @@ -239,12 +259,25 @@ set(VM_SOURCES_WKS ${VM_SOURCES_GDBJIT} ) +set(GC_SOURCES_WKS + ${GC_SOURCES_DAC_AND_WKS_COMMON} + ../gc/gchandletable.cpp + ../gc/gceesvr.cpp + ../gc/gceewks.cpp + ../gc/handletablecache.cpp) + if(FEATURE_EVENT_TRACE) list(APPEND VM_SOURCES_WKS eventtrace.cpp ) endif(FEATURE_EVENT_TRACE) +if(FEATURE_STANDALONE_GC) + list(APPEND VM_SOURCES_WKS + gcenv.ee.standalone.cpp + ) +endif(FEATURE_STANDALONE_GC) + if(NOT FEATURE_STANDALONE_GC) list(APPEND VM_SOURCES_WKS gcenv.os.cpp @@ -472,6 +505,22 @@ list(APPEND VM_SOURCES_DAC ${VM_SOURCES_DAC_AND_WKS_ARCH} ) +# The default option for FEATURE_STANDALONE_GC builds a standalone +# and non-standalone GC, linking the non-standalone GC into coreclr.dll. +# For testing purposes, FEATURE_STANDALONE_GC_ONLY instead only builds and +# links the non-standalone GC into coreclr.dll. +if (NOT FEATURE_STANDALONE_GC_ONLY) + list(APPEND VM_SOURCES_WKS + ${GC_SOURCES_WKS} + ) +endif(NOT FEATURE_STANDALONE_GC_ONLY) + +# The DAC does need GC sources in order to link correctly, even if +# it's not used. +list(APPEND VM_SOURCES_DAC + ${GC_SOURCES_DAC} +) + convert_to_absolute_path(VM_SOURCES_WKS ${VM_SOURCES_WKS}) convert_to_absolute_path(VM_SOURCES_WKS_ARCH_ASM ${VM_SOURCES_WKS_ARCH_ASM}) convert_to_absolute_path(VM_SOURCES_DAC ${VM_SOURCES_DAC}) diff --git a/src/vm/ceemain.cpp b/src/vm/ceemain.cpp index de6059a6ec..617b022516 100644 --- a/src/vm/ceemain.cpp +++ b/src/vm/ceemain.cpp @@ -2440,6 +2440,101 @@ BOOL ExecuteDLL_ReturnOrThrow(HRESULT hr, BOOL fFromThunk) // Initialize the Garbage Collector // +// Prototype for the function that initialzes the garbage collector. +// Should only be called once: here, during EE startup. +// Returns true if the initialization was successful, false otherwise. +// +// When using a standalone GC, this function is loaded dynamically using +// GetProcAddress. +extern "C" bool InitializeGarbageCollector(IGCToCLR* clrToGC, IGCHeap** gcHeap, IGCHandleManager** gcHandleManager, GcDacVars* gcDacVars); + +#ifdef FEATURE_STANDALONE_GC + +void LoadGarbageCollector() +{ + CONTRACTL { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } CONTRACTL_END; + + TCHAR *standaloneGc = nullptr; + CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_GCStandaloneLocation, &standaloneGc); + HMODULE hMod; + if (!standaloneGc) + { +#ifdef FEATURE_STANDALONE_GC_ONLY + // if the user has set GCUseStandalone but has not given us a standalone location, + // try and load the initialization symbol from the current module. + hMod = GetModuleInst(); +#else + ThrowHR(E_FAIL); +#endif // FEATURE_STANDALONE_GC_ONLY + } + else + { + hMod = CLRLoadLibrary(standaloneGc); + } + + if (!hMod) + { + ThrowHR(E_FAIL); + } + + InitializeGarbageCollectorFunction igcf = (InitializeGarbageCollectorFunction)GetProcAddress(hMod, INITIALIZE_GC_FUNCTION_NAME); + if (!igcf) + { + ThrowHR(E_FAIL); + } + + // at this point we are committing to using the standalone GC + // given to us. + IGCToCLR* gcToClr = new (nothrow) standalone::GCToEEInterface(); + if (!gcToClr) + { + ThrowOutOfMemory(); + } + + IGCHandleManager *pGcHandleManager; + IGCHeap *pGCHeap; + if (!igcf(gcToClr, &pGCHeap, &pGcHandleManager, &g_gc_dac_vars)) + { + ThrowOutOfMemory(); + } + + assert(pGCHeap != nullptr); + assert(pGcHandleManager != nullptr); + g_pGCHeap = pGCHeap; + g_pGCHandleManager = pGcHandleManager; + g_gcDacGlobals = &g_gc_dac_vars; +} + +#endif // FEATURE_STANDALONE_GC + +void LoadStaticGarbageCollector() +{ + CONTRACTL{ + THROWS; + GC_TRIGGERS; + MODE_ANY; + } CONTRACTL_END; + + IGCHandleManager *pGcHandleManager; + IGCHeap *pGCHeap; + + if (!InitializeGarbageCollector(nullptr, &pGCHeap, &pGcHandleManager, &g_gc_dac_vars)) + { + ThrowOutOfMemory(); + } + + assert(pGCHeap != nullptr); + assert(pGcHandleManager != nullptr); + g_pGCHeap = pGCHeap; + g_pGCHandleManager = pGcHandleManager; + g_gcDacGlobals = &g_gc_dac_vars; +} + + void InitializeGarbageCollector() { CONTRACTL{ @@ -2463,25 +2558,19 @@ void InitializeGarbageCollector() g_pFreeObjectMethodTable->SetComponentSize(1); #ifdef FEATURE_STANDALONE_GC - IGCToCLR* gcToClr = new (nothrow) GCToEEInterface(); - if (!gcToClr) - ThrowOutOfMemory(); -#else - IGCToCLR* gcToClr = nullptr; -#endif - - IGCHandleManager *pGcHandleManager; - - IGCHeap *pGCHeap; - if (!InitializeGarbageCollector(gcToClr, &pGCHeap, &pGcHandleManager, &g_gc_dac_vars)) + if (CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_GCUseStandalone) +#ifdef FEATURE_STANDALONE_GC_ONLY + || true +#endif // FEATURE_STANDALONE_GC_ONLY + ) { - ThrowOutOfMemory(); + LoadGarbageCollector(); + } + else +#endif // FEATURE_STANDALONE_GC + { + LoadStaticGarbageCollector(); } - - assert(pGCHeap != nullptr); - g_pGCHeap = pGCHeap; - g_pGCHandleManager = pGcHandleManager; - g_gcDacGlobals = &g_gc_dac_vars; // Apparently the Windows linker removes global variables if they are never // read from, which is a problem for g_gcDacGlobals since it's expected that diff --git a/src/vm/gcenv.ee.common.cpp b/src/vm/gcenv.ee.common.cpp new file mode 100644 index 0000000000..ca7349091f --- /dev/null +++ b/src/vm/gcenv.ee.common.cpp @@ -0,0 +1,394 @@ +// 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 "common.h" +#include "gcenv.h" + +#if defined(WIN64EXCEPTIONS) + +struct FindFirstInterruptiblePointState +{ + unsigned offs; + unsigned endOffs; + unsigned returnOffs; +}; + +bool FindFirstInterruptiblePointStateCB( + UINT32 startOffset, + UINT32 stopOffset, + LPVOID hCallback) +{ + FindFirstInterruptiblePointState* pState = (FindFirstInterruptiblePointState*)hCallback; + + _ASSERTE(startOffset < stopOffset); + _ASSERTE(pState->offs < pState->endOffs); + + if (stopOffset <= pState->offs) + { + // The range ends before the requested offset. + return false; + } + + // The offset is in the range. + if (startOffset <= pState->offs && + pState->offs < stopOffset) + { + pState->returnOffs = pState->offs; + return true; + } + + // The range is completely after the desired offset. We use the range start offset, if + // it comes before the given endOffs. We assume that the callback is called with ranges + // in increasing order, so earlier ones are reported before later ones. That is, if we + // get to this case, it will be the closest interruptible range after the requested + // offset. + + _ASSERTE(pState->offs < startOffset); + if (startOffset < pState->endOffs) + { + pState->returnOffs = startOffset; + return true; + } + + return false; +} + +// Find the first interruptible point in the range [offs .. endOffs) (the beginning of the range is inclusive, +// the end is exclusive). Return -1 if no such point exists. +unsigned FindFirstInterruptiblePoint(CrawlFrame* pCF, unsigned offs, unsigned endOffs) +{ +#ifdef USE_GC_INFO_DECODER + GCInfoToken gcInfoToken = pCF->GetGCInfoToken(); + GcInfoDecoder gcInfoDecoder(gcInfoToken, DECODE_FOR_RANGES_CALLBACK); + + FindFirstInterruptiblePointState state; + state.offs = offs; + state.endOffs = endOffs; + state.returnOffs = -1; + + gcInfoDecoder.EnumerateInterruptibleRanges(&FindFirstInterruptiblePointStateCB, &state); + + return state.returnOffs; +#else + PORTABILITY_ASSERT("FindFirstInterruptiblePoint"); + return -1; +#endif // USE_GC_INFO_DECODER +} + +#endif // WIN64EXCEPTIONS + +//----------------------------------------------------------------------------- +// Determine whether we should report the generic parameter context +// +// This is meant to detect the situation where a ThreadAbortException is raised +// in the prolog of a managed method, before the location for the generics +// context has been initialized; when such a TAE is raised, we are open to a +// race with the GC (e.g. while creating the managed object for the TAE). +// The GC would cause a stack walk, and if we report the stack location for +// the generic param context at this time we'd crash. +// The long term solution is to avoid raising TAEs in any non-GC safe points, +// and to additionally ensure that we do not expose the runtime to TAE +// starvation. +inline bool SafeToReportGenericParamContext(CrawlFrame* pCF) +{ + LIMITED_METHOD_CONTRACT; + if (!pCF->IsFrameless() || !(pCF->IsActiveFrame() || pCF->IsInterrupted())) + { + return true; + } + +#ifndef USE_GC_INFO_DECODER + + ICodeManager * pEECM = pCF->GetCodeManager(); + if (pEECM != NULL && pEECM->IsInPrologOrEpilog(pCF->GetRelOffset(), pCF->GetGCInfoToken(), NULL)) + { + return false; + } + +#else // USE_GC_INFO_DECODER + + GcInfoDecoder gcInfoDecoder(pCF->GetGCInfoToken(), + DECODE_PROLOG_LENGTH); + UINT32 prologLength = gcInfoDecoder.GetPrologSize(); + if (pCF->GetRelOffset() < prologLength) + { + return false; + } + +#endif // USE_GC_INFO_DECODER + + return true; +} + +/* + * GcEnumObject() + * + * This is the JIT compiler (or any remote code manager) + * GC enumeration callback + */ + +void GcEnumObject(LPVOID pData, OBJECTREF *pObj, uint32_t flags) +{ + Object ** ppObj = (Object **)pObj; + GCCONTEXT * pCtx = (GCCONTEXT *) pData; + + // Since we may be asynchronously walking another thread's stack, + // check (frequently) for stack-buffer-overrun corruptions after + // any long operation + if (pCtx->cf != NULL) + pCtx->cf->CheckGSCookies(); + + // + // Sanity check that the flags contain only these three values + // + assert((flags & ~(GC_CALL_INTERIOR|GC_CALL_PINNED|GC_CALL_CHECK_APP_DOMAIN)) == 0); + + // for interior pointers, we optimize the case in which + // it points into the current threads stack area + // + if (flags & GC_CALL_INTERIOR) + PromoteCarefully(pCtx->f, ppObj, pCtx->sc, flags); + else + (pCtx->f)(ppObj, pCtx->sc, flags); +} + +//----------------------------------------------------------------------------- +void GcReportLoaderAllocator(promote_func* fn, ScanContext* sc, LoaderAllocator *pLoaderAllocator) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + SO_TOLERANT; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + if (pLoaderAllocator != NULL && pLoaderAllocator->IsCollectible()) + { + Object *refCollectionObject = OBJECTREFToObject(pLoaderAllocator->GetExposedObject()); + +#ifdef _DEBUG + Object *oldObj = refCollectionObject; +#endif + + _ASSERTE(refCollectionObject != NULL); + fn(&refCollectionObject, sc, CHECK_APP_DOMAIN); + + // We are reporting the location of a local variable, assert it doesn't change. + _ASSERTE(oldObj == refCollectionObject); + } +} + +//----------------------------------------------------------------------------- +StackWalkAction GcStackCrawlCallBack(CrawlFrame* pCF, VOID* pData) +{ + // + // KEEP IN SYNC WITH DacStackReferenceWalker::Callback in debug\daccess\daccess.cpp + // + + Frame *pFrame; + GCCONTEXT *gcctx = (GCCONTEXT*) pData; + +#if CHECK_APP_DOMAIN_LEAKS + gcctx->sc->pCurrentDomain = pCF->GetAppDomain(); +#endif //CHECK_APP_DOMAIN_LEAKS + +#ifdef FEATURE_APPDOMAIN_RESOURCE_MONITORING + if (g_fEnableARM) + { + gcctx->sc->pCurrentDomain = pCF->GetAppDomain(); + } +#endif //FEATURE_APPDOMAIN_RESOURCE_MONITORING + + MethodDesc *pMD = pCF->GetFunction(); + +#ifdef GC_PROFILING + gcctx->sc->pMD = pMD; +#endif //GC_PROFILING + + // Clear it on exit so that we never have a stale CrawlFrame + ResetPointerHolder<CrawlFrame*> rph(&gcctx->cf); + // put it somewhere so that GcEnumObject can get to it. + gcctx->cf = pCF; + + bool fReportGCReferences = true; +#if defined(WIN64EXCEPTIONS) + // We may have unwound this crawlFrame and thus, shouldn't report the invalid + // references it may contain. + fReportGCReferences = pCF->ShouldCrawlframeReportGCReferences(); +#endif // defined(WIN64EXCEPTIONS) + + if (fReportGCReferences) + { + if (pCF->IsFrameless()) + { + ICodeManager * pCM = pCF->GetCodeManager(); + _ASSERTE(pCM != NULL); + + unsigned flags = pCF->GetCodeManagerFlags(); + + #ifdef _TARGET_X86_ + STRESS_LOG3(LF_GCROOTS, LL_INFO1000, "Scanning Frameless method %pM EIP = %p &EIP = %p\n", + pMD, GetControlPC(pCF->GetRegisterSet()), pCF->GetRegisterSet()->PCTAddr); + #else + STRESS_LOG2(LF_GCROOTS, LL_INFO1000, "Scanning Frameless method %pM ControlPC = %p\n", + pMD, GetControlPC(pCF->GetRegisterSet())); + #endif + + _ASSERTE(pMD != 0); + + #ifdef _DEBUG + LOG((LF_GCROOTS, LL_INFO1000, "Scanning Frame for method %s:%s\n", + pMD->m_pszDebugClassName, pMD->m_pszDebugMethodName)); + #endif // _DEBUG + + DWORD relOffsetOverride = NO_OVERRIDE_OFFSET; +#if defined(WIN64EXCEPTIONS) && defined(USE_GC_INFO_DECODER) + if (pCF->ShouldParentToFuncletUseUnwindTargetLocationForGCReporting()) + { + GCInfoToken gcInfoToken = pCF->GetGCInfoToken(); + GcInfoDecoder _gcInfoDecoder( + gcInfoToken, + DECODE_CODE_LENGTH + ); + + if(_gcInfoDecoder.WantsReportOnlyLeaf()) + { + // We're in a special case of unwinding from a funclet, and resuming execution in + // another catch funclet associated with same parent function. We need to report roots. + // Reporting at the original throw site gives incorrect liveness information. We choose to + // report the liveness information at the first interruptible instruction of the catch funclet + // that we are going to execute. We also only report stack slots, since no registers can be + // live at the first instruction of a handler, except the catch object, which the VM protects + // specially. If the catch funclet has not interruptible point, we fall back and just report + // what we used to: at the original throw instruction. This might lead to bad GC behavior + // if the liveness is not correct. + const EE_ILEXCEPTION_CLAUSE& ehClauseForCatch = pCF->GetEHClauseForCatch(); + relOffsetOverride = FindFirstInterruptiblePoint(pCF, ehClauseForCatch.HandlerStartPC, + ehClauseForCatch.HandlerEndPC); + _ASSERTE(relOffsetOverride != NO_OVERRIDE_OFFSET); + + STRESS_LOG3(LF_GCROOTS, LL_INFO1000, "Setting override offset = %u for method %pM ControlPC = %p\n", + relOffsetOverride, pMD, GetControlPC(pCF->GetRegisterSet())); + } + + } +#endif // WIN64EXCEPTIONS && USE_GC_INFO_DECODER + + pCM->EnumGcRefs(pCF->GetRegisterSet(), + pCF->GetCodeInfo(), + flags, + GcEnumObject, + pData, + relOffsetOverride); + + } + else + { + Frame * pFrame = pCF->GetFrame(); + + STRESS_LOG3(LF_GCROOTS, LL_INFO1000, + "Scanning ExplicitFrame %p AssocMethod = %pM frameVTable = %pV\n", + pFrame, pFrame->GetFunction(), *((void**) pFrame)); + pFrame->GcScanRoots( gcctx->f, gcctx->sc); + } + } + + + // If we're executing a LCG dynamic method then we must promote the associated resolver to ensure it + // doesn't get collected and yank the method code out from under us). + + // Be careful to only promote the reference -- we can also be called to relocate the reference and + // that can lead to all sorts of problems since we could be racing for the relocation with the long + // weak handle we recover the reference from. Promoting the reference is enough, the handle in the + // reference will be relocated properly as long as we keep it alive till the end of the collection + // as long as the reference is actually maintained by the long weak handle. + if (pMD && gcctx->sc->promotion) + { + BOOL fMaybeCollectibleMethod = TRUE; + + // If this is a frameless method then the jitmanager can answer the question of whether + // or not this is LCG simply by looking at the heap where the code lives, however there + // is also the prestub case where we need to explicitly look at the MD for stuff that isn't + // ngen'd + if (pCF->IsFrameless()) + { + fMaybeCollectibleMethod = ExecutionManager::IsCollectibleMethod(pCF->GetMethodToken()); + } + + if (fMaybeCollectibleMethod && pMD->IsLCGMethod()) + { + Object *refResolver = OBJECTREFToObject(pMD->AsDynamicMethodDesc()->GetLCGMethodResolver()->GetManagedResolver()); +#ifdef _DEBUG + Object *oldObj = refResolver; +#endif + _ASSERTE(refResolver != NULL); + (*gcctx->f)(&refResolver, gcctx->sc, CHECK_APP_DOMAIN); + _ASSERTE(!pMD->IsSharedByGenericInstantiations()); + + // We are reporting the location of a local variable, assert it doesn't change. + _ASSERTE(oldObj == refResolver); + } + else + { + if (fMaybeCollectibleMethod) + { + GcReportLoaderAllocator(gcctx->f, gcctx->sc, pMD->GetLoaderAllocator()); + } + + if (fReportGCReferences) + { + GenericParamContextType paramContextType = GENERIC_PARAM_CONTEXT_NONE; + + if (pCF->IsFrameless()) + { + // We need to grab the Context Type here because there are cases where the MethodDesc + // is shared, and thus indicates there should be an instantion argument, but the JIT + // was still allowed to optimize it away and we won't grab it below because we're not + // reporting any references from this frame. + paramContextType = pCF->GetCodeManager()->GetParamContextType(pCF->GetRegisterSet(), pCF->GetCodeInfo()); + } + else + { + if (pMD->RequiresInstMethodDescArg()) + paramContextType = GENERIC_PARAM_CONTEXT_METHODDESC; + else if (pMD->RequiresInstMethodTableArg()) + paramContextType = GENERIC_PARAM_CONTEXT_METHODTABLE; + } + + if (SafeToReportGenericParamContext(pCF)) + { + // Handle the case where the method is a static shared generic method and we need to keep the type + // of the generic parameters alive + if (paramContextType == GENERIC_PARAM_CONTEXT_METHODDESC) + { + MethodDesc *pMDReal = dac_cast<PTR_MethodDesc>(pCF->GetParamTypeArg()); + _ASSERTE((pMDReal != NULL) || !pCF->IsFrameless()); + if (pMDReal != NULL) + { + GcReportLoaderAllocator(gcctx->f, gcctx->sc, pMDReal->GetLoaderAllocator()); + } + } + else if (paramContextType == GENERIC_PARAM_CONTEXT_METHODTABLE) + { + MethodTable *pMTReal = dac_cast<PTR_MethodTable>(pCF->GetParamTypeArg()); + _ASSERTE((pMTReal != NULL) || !pCF->IsFrameless()); + if (pMTReal != NULL) + { + GcReportLoaderAllocator(gcctx->f, gcctx->sc, pMTReal->GetLoaderAllocator()); + } + } + } + } + } + } + + // Since we may be asynchronously walking another thread's stack, + // check (frequently) for stack-buffer-overrun corruptions after + // any long operation + pCF->CheckGSCookies(); + + return SWA_CONTINUE; +}
\ No newline at end of file diff --git a/src/vm/gcenv.ee.cpp b/src/vm/gcenv.ee.cpp index 63c4ffea10..8be0b29d58 100644 --- a/src/vm/gcenv.ee.cpp +++ b/src/vm/gcenv.ee.cpp @@ -11,33 +11,6 @@ * */ -#include "common.h" - -#include "gcenv.h" - -#ifdef FEATURE_STANDALONE_GC -#include "gcenv.ee.h" -#else -#include "../gc/env/gcenv.ee.h" -#endif // FEATURE_STANDALONE_GC - -#include "threadsuspend.h" - -#ifdef FEATURE_COMINTEROP -#include "runtimecallablewrapper.h" -#include "rcwwalker.h" -#include "comcallablewrapper.h" -#endif // FEATURE_COMINTEROP - -// the method table for the WeakReference class -extern MethodTable* pWeakReferenceMT; - -// The canonical method table for WeakReference<T> -extern MethodTable* pWeakReferenceOfTCanonMT; - -// Finalizes a weak reference directly. -extern void FinalizeWeakReference(Object* obj); - void GCToEEInterface::SuspendEE(SUSPEND_REASON reason) { WRAPPER_NO_CONTRACT; @@ -57,394 +30,6 @@ void GCToEEInterface::RestartEE(bool bFinishedGC) ThreadSuspend::RestartEE(bFinishedGC, TRUE); } -/* - * GcEnumObject() - * - * This is the JIT compiler (or any remote code manager) - * GC enumeration callback - */ - -void GcEnumObject(LPVOID pData, OBJECTREF *pObj, uint32_t flags) -{ - Object ** ppObj = (Object **)pObj; - GCCONTEXT * pCtx = (GCCONTEXT *) pData; - - // Since we may be asynchronously walking another thread's stack, - // check (frequently) for stack-buffer-overrun corruptions after - // any long operation - if (pCtx->cf != NULL) - pCtx->cf->CheckGSCookies(); - - // - // Sanity check that the flags contain only these three values - // - assert((flags & ~(GC_CALL_INTERIOR|GC_CALL_PINNED|GC_CALL_CHECK_APP_DOMAIN)) == 0); - - // for interior pointers, we optimize the case in which - // it points into the current threads stack area - // - if (flags & GC_CALL_INTERIOR) - PromoteCarefully(pCtx->f, ppObj, pCtx->sc, flags); - else - (pCtx->f)(ppObj, pCtx->sc, flags); -} - -//----------------------------------------------------------------------------- -void GcReportLoaderAllocator(promote_func* fn, ScanContext* sc, LoaderAllocator *pLoaderAllocator) -{ - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - SO_TOLERANT; - MODE_COOPERATIVE; - } - CONTRACTL_END; - - if (pLoaderAllocator != NULL && pLoaderAllocator->IsCollectible()) - { - Object *refCollectionObject = OBJECTREFToObject(pLoaderAllocator->GetExposedObject()); - -#ifdef _DEBUG - Object *oldObj = refCollectionObject; -#endif - - _ASSERTE(refCollectionObject != NULL); - fn(&refCollectionObject, sc, CHECK_APP_DOMAIN); - - // We are reporting the location of a local variable, assert it doesn't change. - _ASSERTE(oldObj == refCollectionObject); - } -} - -//----------------------------------------------------------------------------- -// Determine whether we should report the generic parameter context -// -// This is meant to detect the situation where a ThreadAbortException is raised -// in the prolog of a managed method, before the location for the generics -// context has been initialized; when such a TAE is raised, we are open to a -// race with the GC (e.g. while creating the managed object for the TAE). -// The GC would cause a stack walk, and if we report the stack location for -// the generic param context at this time we'd crash. -// The long term solution is to avoid raising TAEs in any non-GC safe points, -// and to additionally ensure that we do not expose the runtime to TAE -// starvation. -inline bool SafeToReportGenericParamContext(CrawlFrame* pCF) -{ - LIMITED_METHOD_CONTRACT; - if (!pCF->IsFrameless() || !(pCF->IsActiveFrame() || pCF->IsInterrupted())) - { - return true; - } - -#ifndef USE_GC_INFO_DECODER - - ICodeManager * pEECM = pCF->GetCodeManager(); - if (pEECM != NULL && pEECM->IsInPrologOrEpilog(pCF->GetRelOffset(), pCF->GetGCInfoToken(), NULL)) - { - return false; - } - -#else // USE_GC_INFO_DECODER - - GcInfoDecoder gcInfoDecoder(pCF->GetGCInfoToken(), - DECODE_PROLOG_LENGTH); - UINT32 prologLength = gcInfoDecoder.GetPrologSize(); - if (pCF->GetRelOffset() < prologLength) - { - return false; - } - -#endif // USE_GC_INFO_DECODER - - return true; -} - -#if defined(WIN64EXCEPTIONS) - -struct FindFirstInterruptiblePointState -{ - unsigned offs; - unsigned endOffs; - unsigned returnOffs; -}; - -bool FindFirstInterruptiblePointStateCB( - UINT32 startOffset, - UINT32 stopOffset, - LPVOID hCallback) -{ - FindFirstInterruptiblePointState* pState = (FindFirstInterruptiblePointState*)hCallback; - - _ASSERTE(startOffset < stopOffset); - _ASSERTE(pState->offs < pState->endOffs); - - if (stopOffset <= pState->offs) - { - // The range ends before the requested offset. - return false; - } - - // The offset is in the range. - if (startOffset <= pState->offs && - pState->offs < stopOffset) - { - pState->returnOffs = pState->offs; - return true; - } - - // The range is completely after the desired offset. We use the range start offset, if - // it comes before the given endOffs. We assume that the callback is called with ranges - // in increasing order, so earlier ones are reported before later ones. That is, if we - // get to this case, it will be the closest interruptible range after the requested - // offset. - - _ASSERTE(pState->offs < startOffset); - if (startOffset < pState->endOffs) - { - pState->returnOffs = startOffset; - return true; - } - - return false; -} - -// Find the first interruptible point in the range [offs .. endOffs) (the beginning of the range is inclusive, -// the end is exclusive). Return -1 if no such point exists. -unsigned FindFirstInterruptiblePoint(CrawlFrame* pCF, unsigned offs, unsigned endOffs) -{ -#ifdef USE_GC_INFO_DECODER - GCInfoToken gcInfoToken = pCF->GetGCInfoToken(); - GcInfoDecoder gcInfoDecoder(gcInfoToken, DECODE_FOR_RANGES_CALLBACK); - - FindFirstInterruptiblePointState state; - state.offs = offs; - state.endOffs = endOffs; - state.returnOffs = -1; - - gcInfoDecoder.EnumerateInterruptibleRanges(&FindFirstInterruptiblePointStateCB, &state); - - return state.returnOffs; -#else - PORTABILITY_ASSERT("FindFirstInterruptiblePoint"); - return -1; -#endif // USE_GC_INFO_DECODER -} - -#endif // WIN64EXCEPTIONS - -//----------------------------------------------------------------------------- -StackWalkAction GcStackCrawlCallBack(CrawlFrame* pCF, VOID* pData) -{ - // - // KEEP IN SYNC WITH DacStackReferenceWalker::Callback in debug\daccess\daccess.cpp - // - - Frame *pFrame; - GCCONTEXT *gcctx = (GCCONTEXT*) pData; - -#if CHECK_APP_DOMAIN_LEAKS - gcctx->sc->pCurrentDomain = pCF->GetAppDomain(); -#endif //CHECK_APP_DOMAIN_LEAKS - -#ifdef FEATURE_APPDOMAIN_RESOURCE_MONITORING - if (g_fEnableARM) - { - gcctx->sc->pCurrentDomain = pCF->GetAppDomain(); - } -#endif //FEATURE_APPDOMAIN_RESOURCE_MONITORING - - MethodDesc *pMD = pCF->GetFunction(); - -#ifdef GC_PROFILING - gcctx->sc->pMD = pMD; -#endif //GC_PROFILING - - // Clear it on exit so that we never have a stale CrawlFrame - ResetPointerHolder<CrawlFrame*> rph(&gcctx->cf); - // put it somewhere so that GcEnumObject can get to it. - gcctx->cf = pCF; - - bool fReportGCReferences = true; -#if defined(WIN64EXCEPTIONS) - // We may have unwound this crawlFrame and thus, shouldn't report the invalid - // references it may contain. - fReportGCReferences = pCF->ShouldCrawlframeReportGCReferences(); -#endif // defined(WIN64EXCEPTIONS) - - if (fReportGCReferences) - { - if (pCF->IsFrameless()) - { - ICodeManager * pCM = pCF->GetCodeManager(); - _ASSERTE(pCM != NULL); - - unsigned flags = pCF->GetCodeManagerFlags(); - - #ifdef _TARGET_X86_ - STRESS_LOG3(LF_GCROOTS, LL_INFO1000, "Scanning Frameless method %pM EIP = %p &EIP = %p\n", - pMD, GetControlPC(pCF->GetRegisterSet()), pCF->GetRegisterSet()->PCTAddr); - #else - STRESS_LOG2(LF_GCROOTS, LL_INFO1000, "Scanning Frameless method %pM ControlPC = %p\n", - pMD, GetControlPC(pCF->GetRegisterSet())); - #endif - - _ASSERTE(pMD != 0); - - #ifdef _DEBUG - LOG((LF_GCROOTS, LL_INFO1000, "Scanning Frame for method %s:%s\n", - pMD->m_pszDebugClassName, pMD->m_pszDebugMethodName)); - #endif // _DEBUG - - DWORD relOffsetOverride = NO_OVERRIDE_OFFSET; -#if defined(WIN64EXCEPTIONS) && defined(USE_GC_INFO_DECODER) - if (pCF->ShouldParentToFuncletUseUnwindTargetLocationForGCReporting()) - { - GCInfoToken gcInfoToken = pCF->GetGCInfoToken(); - GcInfoDecoder _gcInfoDecoder( - gcInfoToken, - DECODE_CODE_LENGTH - ); - - if(_gcInfoDecoder.WantsReportOnlyLeaf()) - { - // We're in a special case of unwinding from a funclet, and resuming execution in - // another catch funclet associated with same parent function. We need to report roots. - // Reporting at the original throw site gives incorrect liveness information. We choose to - // report the liveness information at the first interruptible instruction of the catch funclet - // that we are going to execute. We also only report stack slots, since no registers can be - // live at the first instruction of a handler, except the catch object, which the VM protects - // specially. If the catch funclet has not interruptible point, we fall back and just report - // what we used to: at the original throw instruction. This might lead to bad GC behavior - // if the liveness is not correct. - const EE_ILEXCEPTION_CLAUSE& ehClauseForCatch = pCF->GetEHClauseForCatch(); - relOffsetOverride = FindFirstInterruptiblePoint(pCF, ehClauseForCatch.HandlerStartPC, - ehClauseForCatch.HandlerEndPC); - _ASSERTE(relOffsetOverride != NO_OVERRIDE_OFFSET); - - STRESS_LOG3(LF_GCROOTS, LL_INFO1000, "Setting override offset = %u for method %pM ControlPC = %p\n", - relOffsetOverride, pMD, GetControlPC(pCF->GetRegisterSet())); - } - - } -#endif // WIN64EXCEPTIONS && USE_GC_INFO_DECODER - - pCM->EnumGcRefs(pCF->GetRegisterSet(), - pCF->GetCodeInfo(), - flags, - GcEnumObject, - pData, - relOffsetOverride); - - } - else - { - Frame * pFrame = pCF->GetFrame(); - - STRESS_LOG3(LF_GCROOTS, LL_INFO1000, - "Scanning ExplicitFrame %p AssocMethod = %pM frameVTable = %pV\n", - pFrame, pFrame->GetFunction(), *((void**) pFrame)); - pFrame->GcScanRoots( gcctx->f, gcctx->sc); - } - } - - - // If we're executing a LCG dynamic method then we must promote the associated resolver to ensure it - // doesn't get collected and yank the method code out from under us). - - // Be careful to only promote the reference -- we can also be called to relocate the reference and - // that can lead to all sorts of problems since we could be racing for the relocation with the long - // weak handle we recover the reference from. Promoting the reference is enough, the handle in the - // reference will be relocated properly as long as we keep it alive till the end of the collection - // as long as the reference is actually maintained by the long weak handle. - if (pMD && gcctx->sc->promotion) - { - BOOL fMaybeCollectibleMethod = TRUE; - - // If this is a frameless method then the jitmanager can answer the question of whether - // or not this is LCG simply by looking at the heap where the code lives, however there - // is also the prestub case where we need to explicitly look at the MD for stuff that isn't - // ngen'd - if (pCF->IsFrameless()) - { - fMaybeCollectibleMethod = ExecutionManager::IsCollectibleMethod(pCF->GetMethodToken()); - } - - if (fMaybeCollectibleMethod && pMD->IsLCGMethod()) - { - Object *refResolver = OBJECTREFToObject(pMD->AsDynamicMethodDesc()->GetLCGMethodResolver()->GetManagedResolver()); -#ifdef _DEBUG - Object *oldObj = refResolver; -#endif - _ASSERTE(refResolver != NULL); - (*gcctx->f)(&refResolver, gcctx->sc, CHECK_APP_DOMAIN); - _ASSERTE(!pMD->IsSharedByGenericInstantiations()); - - // We are reporting the location of a local variable, assert it doesn't change. - _ASSERTE(oldObj == refResolver); - } - else - { - if (fMaybeCollectibleMethod) - { - GcReportLoaderAllocator(gcctx->f, gcctx->sc, pMD->GetLoaderAllocator()); - } - - if (fReportGCReferences) - { - GenericParamContextType paramContextType = GENERIC_PARAM_CONTEXT_NONE; - - if (pCF->IsFrameless()) - { - // We need to grab the Context Type here because there are cases where the MethodDesc - // is shared, and thus indicates there should be an instantion argument, but the JIT - // was still allowed to optimize it away and we won't grab it below because we're not - // reporting any references from this frame. - paramContextType = pCF->GetCodeManager()->GetParamContextType(pCF->GetRegisterSet(), pCF->GetCodeInfo()); - } - else - { - if (pMD->RequiresInstMethodDescArg()) - paramContextType = GENERIC_PARAM_CONTEXT_METHODDESC; - else if (pMD->RequiresInstMethodTableArg()) - paramContextType = GENERIC_PARAM_CONTEXT_METHODTABLE; - } - - if (SafeToReportGenericParamContext(pCF)) - { - // Handle the case where the method is a static shared generic method and we need to keep the type - // of the generic parameters alive - if (paramContextType == GENERIC_PARAM_CONTEXT_METHODDESC) - { - MethodDesc *pMDReal = dac_cast<PTR_MethodDesc>(pCF->GetParamTypeArg()); - _ASSERTE((pMDReal != NULL) || !pCF->IsFrameless()); - if (pMDReal != NULL) - { - GcReportLoaderAllocator(gcctx->f, gcctx->sc, pMDReal->GetLoaderAllocator()); - } - } - else if (paramContextType == GENERIC_PARAM_CONTEXT_METHODTABLE) - { - MethodTable *pMTReal = dac_cast<PTR_MethodTable>(pCF->GetParamTypeArg()); - _ASSERTE((pMTReal != NULL) || !pCF->IsFrameless()); - if (pMTReal != NULL) - { - GcReportLoaderAllocator(gcctx->f, gcctx->sc, pMTReal->GetLoaderAllocator()); - } - } - } - } - } - } - - // Since we may be asynchronously walking another thread's stack, - // check (frequently) for stack-buffer-overrun corruptions after - // any long operation - pCF->CheckGSCookies(); - - return SWA_CONTINUE; -} - VOID GCToEEInterface::SyncBlockCacheWeakPtrScan(HANDLESCANPROC scanProc, uintptr_t lp1, uintptr_t lp2) { CONTRACTL diff --git a/src/vm/gcenv.ee.h b/src/vm/gcenv.ee.h index 9f7df14d22..6c917a5a2b 100644 --- a/src/vm/gcenv.ee.h +++ b/src/vm/gcenv.ee.h @@ -9,6 +9,9 @@ #ifdef FEATURE_STANDALONE_GC +namespace standalone +{ + class GCToEEInterface : public IGCToCLR { public: GCToEEInterface() = default; @@ -51,6 +54,8 @@ public: MethodTable* GetFreeObjectMethodTable(); }; +} // namespace standalone + #endif // FEATURE_STANDALONE_GC #endif // _GCENV_EE_H_ diff --git a/src/vm/gcenv.ee.standalone.cpp b/src/vm/gcenv.ee.standalone.cpp new file mode 100644 index 0000000000..5ba2aca812 --- /dev/null +++ b/src/vm/gcenv.ee.standalone.cpp @@ -0,0 +1,30 @@ +// 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 "common.h" +#include "gcenv.h" +#include "gcenv.ee.h" +#include "threadsuspend.h" + +#ifdef FEATURE_COMINTEROP +#include "runtimecallablewrapper.h" +#include "rcwwalker.h" +#include "comcallablewrapper.h" +#endif // FEATURE_COMINTEROP + +// the method table for the WeakReference class +extern MethodTable* pWeakReferenceMT; + +// The canonical method table for WeakReference<T> +extern MethodTable* pWeakReferenceOfTCanonMT; + +// Finalizes a weak reference directly. +extern void FinalizeWeakReference(Object* obj); + +namespace standalone +{ + +#include "gcenv.ee.cpp" + +} // namespace standalone
\ No newline at end of file diff --git a/src/vm/gcenv.ee.static.cpp b/src/vm/gcenv.ee.static.cpp new file mode 100644 index 0000000000..240e325a9e --- /dev/null +++ b/src/vm/gcenv.ee.static.cpp @@ -0,0 +1,25 @@ +// 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 "common.h" +#include "gcenv.h" +#include "../gc/env/gcenv.ee.h" +#include "threadsuspend.h" + +#ifdef FEATURE_COMINTEROP +#include "runtimecallablewrapper.h" +#include "rcwwalker.h" +#include "comcallablewrapper.h" +#endif // FEATURE_COMINTEROP + +// the method table for the WeakReference class +extern MethodTable* pWeakReferenceMT; + +// The canonical method table for WeakReference<T> +extern MethodTable* pWeakReferenceOfTCanonMT; + +// Finalizes a weak reference directly. +extern void FinalizeWeakReference(Object* obj); + +#include "gcenv.ee.cpp"
\ No newline at end of file |