summaryrefslogtreecommitdiff
path: root/src/vm
diff options
context:
space:
mode:
authorSean Gillespie <segilles@microsoft.com>2017-05-17 15:07:34 -0700
committerSean Gillespie <segilles@microsoft.com>2017-06-01 10:19:59 -0700
commit1a183684b1ecf63ece8a2fd80173f083c0deea52 (patch)
tree907b31b417732768b28e80adfb4d920497f0d3f0 /src/vm
parentdde63bc1aa39aabae77fb89aad583483965c523e (diff)
downloadcoreclr-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
Diffstat (limited to 'src/vm')
-rw-r--r--src/vm/CMakeLists.txt55
-rw-r--r--src/vm/ceemain.cpp123
-rw-r--r--src/vm/gcenv.ee.common.cpp394
-rw-r--r--src/vm/gcenv.ee.cpp415
-rw-r--r--src/vm/gcenv.ee.h5
-rw-r--r--src/vm/gcenv.ee.standalone.cpp30
-rw-r--r--src/vm/gcenv.ee.static.cpp25
7 files changed, 612 insertions, 435 deletions
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