summaryrefslogtreecommitdiff
path: root/src/vm/codeversion.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/vm/codeversion.cpp')
-rw-r--r--src/vm/codeversion.cpp2862
1 files changed, 2862 insertions, 0 deletions
diff --git a/src/vm/codeversion.cpp b/src/vm/codeversion.cpp
new file mode 100644
index 0000000000..10d3013f35
--- /dev/null
+++ b/src/vm/codeversion.cpp
@@ -0,0 +1,2862 @@
+// 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.
+// ===========================================================================
+// File: CodeVersion.cpp
+//
+// ===========================================================================
+
+#include "common.h"
+#include "codeversion.h"
+
+#ifdef FEATURE_CODE_VERSIONING
+#include "threadsuspend.h"
+#include "methoditer.h"
+#include "../debug/ee/debugger.h"
+#include "../debug/ee/walker.h"
+#include "../debug/ee/controller.h"
+#endif // FEATURE_CODE_VERSIONING
+
+#ifndef FEATURE_CODE_VERSIONING
+
+//
+// When not using code versioning we've got a minimal implementation of
+// NativeCodeVersion that simply wraps a MethodDesc* with no additional
+// versioning information
+//
+
+NativeCodeVersion::NativeCodeVersion(const NativeCodeVersion & rhs) : m_pMethod(rhs.m_pMethod) {}
+NativeCodeVersion::NativeCodeVersion(PTR_MethodDesc pMethod) : m_pMethod(pMethod) {}
+BOOL NativeCodeVersion::IsNull() const { return m_pMethod == NULL; }
+PTR_MethodDesc NativeCodeVersion::GetMethodDesc() const { return m_pMethod; }
+PCODE NativeCodeVersion::GetNativeCode() const { return m_pMethod->GetNativeCode(); }
+NativeCodeVersionId NativeCodeVersion::GetVersionId() const { return 0; }
+ReJITID NativeCodeVersion::GetILCodeVersionId() const; { return 0; }
+ILCodeVersion NativeCodeVersion::GetILCodeVersion() const { return ILCodeVersion(m_pMethod); }
+#ifndef DACCESS_COMPILE
+BOOL NativeCodeVersion::SetNativeCodeInterlocked(PCODE pCode, PCODE pExpected) { return m_pMethod->SetNativeCodeInterlocked(pCode, pExpected); }
+#endif
+bool NativeCodeVersion::operator==(const NativeCodeVersion & rhs) const { return m_pMethod == rhs.m_pMethod; }
+bool NativeCodeVersion::operator!=(const NativeCodeVersion & rhs) const { return !operator==(rhs); }
+
+
+#else // FEATURE_CODE_VERSIONING
+
+
+// This HRESULT is only used as a private implementation detail. If it escapes through public APIS
+// it is a bug. Corerror.xml has a comment in it reserving this value for our use but it doesn't
+// appear in the public headers.
+
+#define CORPROF_E_RUNTIME_SUSPEND_REQUIRED 0x80131381
+
+#ifndef DACCESS_COMPILE
+NativeCodeVersionNode::NativeCodeVersionNode(NativeCodeVersionId id, MethodDesc* pMethodDesc, ReJITID parentId) :
+ m_pNativeCode(NULL),
+ m_pMethodDesc(pMethodDesc),
+ m_parentId(parentId),
+ m_pNextMethodDescSibling(NULL),
+ m_id(id),
+ m_optTier(NativeCodeVersion::OptimizationTier0),
+ m_flags(0)
+{}
+#endif
+
+#ifdef DEBUG
+BOOL NativeCodeVersionNode::LockOwnedByCurrentThread() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ return GetMethodDesc()->GetCodeVersionManager()->LockOwnedByCurrentThread();
+}
+#endif //DEBUG
+
+PTR_MethodDesc NativeCodeVersionNode::GetMethodDesc() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ return m_pMethodDesc;
+}
+
+PCODE NativeCodeVersionNode::GetNativeCode() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ return m_pNativeCode;
+}
+
+ReJITID NativeCodeVersionNode::GetILVersionId() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ return m_parentId;
+}
+
+ILCodeVersion NativeCodeVersionNode::GetILCodeVersion() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+#ifdef DEBUG
+ if (GetILVersionId() != 0)
+ {
+ _ASSERTE(LockOwnedByCurrentThread());
+ }
+#endif
+ PTR_MethodDesc pMD = GetMethodDesc();
+ return pMD->GetCodeVersionManager()->GetILCodeVersion(pMD, GetILVersionId());
+}
+
+NativeCodeVersionId NativeCodeVersionNode::GetVersionId() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ return m_id;
+}
+
+#ifndef DACCESS_COMPILE
+BOOL NativeCodeVersionNode::SetNativeCodeInterlocked(PCODE pCode, PCODE pExpected)
+{
+ LIMITED_METHOD_CONTRACT;
+ return FastInterlockCompareExchangePointer(&m_pNativeCode,
+ (TADDR&)pCode, (TADDR&)pExpected) == (TADDR&)pExpected;
+}
+#endif
+
+BOOL NativeCodeVersionNode::IsActiveChildVersion() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ _ASSERTE(LockOwnedByCurrentThread());
+ return (m_flags & IsActiveChildFlag) != 0;
+}
+
+#ifndef DACCESS_COMPILE
+void NativeCodeVersionNode::SetActiveChildFlag(BOOL isActive)
+{
+ LIMITED_METHOD_CONTRACT;
+ _ASSERTE(LockOwnedByCurrentThread());
+ if (isActive)
+ {
+ m_flags |= IsActiveChildFlag;
+ }
+ else
+ {
+ m_flags &= ~IsActiveChildFlag;
+ }
+}
+#endif
+
+
+#ifdef FEATURE_TIERED_COMPILATION
+NativeCodeVersion::OptimizationTier NativeCodeVersionNode::GetOptimizationTier() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ return m_optTier.Load();
+}
+#ifndef DACCESS_COMPILE
+void NativeCodeVersionNode::SetOptimizationTier(NativeCodeVersion::OptimizationTier tier)
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ m_optTier.Store(tier);
+}
+#endif
+#endif // FEATURE_TIERED_COMPILATION
+
+NativeCodeVersion::NativeCodeVersion() :
+ m_storageKind(StorageKind::Unknown)
+{}
+
+NativeCodeVersion::NativeCodeVersion(const NativeCodeVersion & rhs) :
+ m_storageKind(rhs.m_storageKind)
+{
+ if(m_storageKind == StorageKind::Explicit)
+ {
+ m_pVersionNode = rhs.m_pVersionNode;
+ }
+ else if(m_storageKind == StorageKind::Synthetic)
+ {
+ m_synthetic = rhs.m_synthetic;
+ }
+}
+
+NativeCodeVersion::NativeCodeVersion(PTR_NativeCodeVersionNode pVersionNode) :
+ m_storageKind(pVersionNode != NULL ? StorageKind::Explicit : StorageKind::Unknown),
+ m_pVersionNode(pVersionNode)
+{}
+
+NativeCodeVersion::NativeCodeVersion(PTR_MethodDesc pMethod) :
+ m_storageKind(pMethod != NULL ? StorageKind::Synthetic : StorageKind::Unknown)
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ m_synthetic.m_pMethodDesc = pMethod;
+}
+
+BOOL NativeCodeVersion::IsNull() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ return m_storageKind == StorageKind::Unknown;
+}
+
+BOOL NativeCodeVersion::IsDefaultVersion() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ return m_storageKind == StorageKind::Synthetic;
+}
+
+PTR_MethodDesc NativeCodeVersion::GetMethodDesc() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ if (m_storageKind == StorageKind::Explicit)
+ {
+ return AsNode()->GetMethodDesc();
+ }
+ else
+ {
+ return m_synthetic.m_pMethodDesc;
+ }
+}
+
+PCODE NativeCodeVersion::GetNativeCode() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ if (m_storageKind == StorageKind::Explicit)
+ {
+ return AsNode()->GetNativeCode();
+ }
+ else
+ {
+ return GetMethodDesc()->GetNativeCode();
+ }
+}
+
+ReJITID NativeCodeVersion::GetILCodeVersionId() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ if (m_storageKind == StorageKind::Explicit)
+ {
+ return AsNode()->GetILVersionId();
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+ILCodeVersion NativeCodeVersion::GetILCodeVersion() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ if (m_storageKind == StorageKind::Explicit)
+ {
+ return AsNode()->GetILCodeVersion();
+ }
+ else
+ {
+ PTR_MethodDesc pMethod = GetMethodDesc();
+ return ILCodeVersion(dac_cast<PTR_Module>(pMethod->GetModule()), pMethod->GetMemberDef());
+ }
+}
+
+NativeCodeVersionId NativeCodeVersion::GetVersionId() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ if (m_storageKind == StorageKind::Explicit)
+ {
+ return AsNode()->GetVersionId();
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+#ifndef DACCESS_COMPILE
+BOOL NativeCodeVersion::SetNativeCodeInterlocked(PCODE pCode, PCODE pExpected)
+{
+ LIMITED_METHOD_CONTRACT;
+ if (m_storageKind == StorageKind::Explicit)
+ {
+ return AsNode()->SetNativeCodeInterlocked(pCode, pExpected);
+ }
+ else
+ {
+ return GetMethodDesc()->SetNativeCodeInterlocked(pCode, pExpected);
+ }
+}
+#endif
+
+BOOL NativeCodeVersion::IsActiveChildVersion() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ if (m_storageKind == StorageKind::Explicit)
+ {
+ return AsNode()->IsActiveChildVersion();
+ }
+ else
+ {
+ MethodDescVersioningState* pMethodVersioningState = GetMethodDescVersioningState();
+ if (pMethodVersioningState == NULL)
+ {
+ return TRUE;
+ }
+ return pMethodVersioningState->IsDefaultVersionActiveChild();
+ }
+}
+
+PTR_MethodDescVersioningState NativeCodeVersion::GetMethodDescVersioningState() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ PTR_MethodDesc pMethodDesc = GetMethodDesc();
+ CodeVersionManager* pCodeVersionManager = pMethodDesc->GetCodeVersionManager();
+ return pCodeVersionManager->GetMethodDescVersioningState(pMethodDesc);
+}
+
+#ifndef DACCESS_COMPILE
+void NativeCodeVersion::SetActiveChildFlag(BOOL isActive)
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ if (m_storageKind == StorageKind::Explicit)
+ {
+ AsNode()->SetActiveChildFlag(isActive);
+ }
+ else
+ {
+ MethodDescVersioningState* pMethodVersioningState = GetMethodDescVersioningState();
+ pMethodVersioningState->SetDefaultVersionActiveChildFlag(isActive);
+ }
+}
+
+MethodDescVersioningState* NativeCodeVersion::GetMethodDescVersioningState()
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ MethodDesc* pMethodDesc = GetMethodDesc();
+ CodeVersionManager* pCodeVersionManager = pMethodDesc->GetCodeVersionManager();
+ return pCodeVersionManager->GetMethodDescVersioningState(pMethodDesc);
+}
+#endif
+
+#ifdef FEATURE_TIERED_COMPILATION
+NativeCodeVersion::OptimizationTier NativeCodeVersion::GetOptimizationTier() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ if (m_storageKind == StorageKind::Explicit)
+ {
+ return AsNode()->GetOptimizationTier();
+ }
+ else
+ {
+ return NativeCodeVersion::OptimizationTier0;
+ }
+}
+
+#ifndef DACCESS_COMPILE
+void NativeCodeVersion::SetOptimizationTier(NativeCodeVersion::OptimizationTier tier)
+{
+ LIMITED_METHOD_CONTRACT;
+ if (m_storageKind == StorageKind::Explicit)
+ {
+ AsNode()->SetOptimizationTier(tier);
+ }
+ else
+ {
+ _ASSERTE(!"Do not call SetOptimizationTier on default code versions - these versions are immutable");
+ }
+}
+#endif
+#endif
+
+PTR_NativeCodeVersionNode NativeCodeVersion::AsNode() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ if (m_storageKind == StorageKind::Explicit)
+ {
+ return m_pVersionNode;
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+#ifndef DACCESS_COMPILE
+PTR_NativeCodeVersionNode NativeCodeVersion::AsNode()
+{
+ LIMITED_METHOD_CONTRACT;
+ if (m_storageKind == StorageKind::Explicit)
+ {
+ return m_pVersionNode;
+ }
+ else
+ {
+ return NULL;
+ }
+}
+#endif
+
+bool NativeCodeVersion::operator==(const NativeCodeVersion & rhs) const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ if (m_storageKind == StorageKind::Explicit)
+ {
+ return (rhs.m_storageKind == StorageKind::Explicit) &&
+ (rhs.AsNode() == AsNode());
+ }
+ else if (m_storageKind == StorageKind::Synthetic)
+ {
+ return (rhs.m_storageKind == StorageKind::Synthetic) &&
+ (m_synthetic.m_pMethodDesc == rhs.m_synthetic.m_pMethodDesc);
+ }
+ else
+ {
+ return rhs.m_storageKind == StorageKind::Unknown;
+ }
+}
+bool NativeCodeVersion::operator!=(const NativeCodeVersion & rhs) const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ return !operator==(rhs);
+}
+
+NativeCodeVersionCollection::NativeCodeVersionCollection(PTR_MethodDesc pMethodDescFilter, ILCodeVersion ilCodeFilter) :
+ m_pMethodDescFilter(pMethodDescFilter),
+ m_ilCodeFilter(ilCodeFilter)
+{
+}
+
+NativeCodeVersionIterator NativeCodeVersionCollection::Begin()
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ return NativeCodeVersionIterator(this);
+}
+NativeCodeVersionIterator NativeCodeVersionCollection::End()
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ return NativeCodeVersionIterator(NULL);
+}
+
+NativeCodeVersionIterator::NativeCodeVersionIterator(NativeCodeVersionCollection* pNativeCodeVersionCollection) :
+ m_stage(IterationStage::Initial),
+ m_pCollection(pNativeCodeVersionCollection),
+ m_pLinkedListCur(dac_cast<PTR_NativeCodeVersionNode>(nullptr))
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ First();
+}
+void NativeCodeVersionIterator::First()
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ if (m_pCollection == NULL)
+ {
+ m_stage = IterationStage::End;
+ }
+ Next();
+}
+void NativeCodeVersionIterator::Next()
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ if (m_stage == IterationStage::Initial)
+ {
+ ILCodeVersion ilCodeFilter = m_pCollection->m_ilCodeFilter;
+ m_stage = IterationStage::ImplicitCodeVersion;
+ if (ilCodeFilter.IsNull() || ilCodeFilter.IsDefaultVersion())
+ {
+ m_cur = NativeCodeVersion(m_pCollection->m_pMethodDescFilter);
+ return;
+ }
+ }
+ if (m_stage == IterationStage::ImplicitCodeVersion)
+ {
+ m_stage = IterationStage::LinkedList;
+ CodeVersionManager* pCodeVersionManager = m_pCollection->m_pMethodDescFilter->GetCodeVersionManager();
+ MethodDescVersioningState* pMethodDescVersioningState = pCodeVersionManager->GetMethodDescVersioningState(m_pCollection->m_pMethodDescFilter);
+ if (pMethodDescVersioningState == NULL)
+ {
+ m_pLinkedListCur = NULL;
+ }
+ else
+ {
+ ILCodeVersion ilCodeFilter = m_pCollection->m_ilCodeFilter;
+ m_pLinkedListCur = pMethodDescVersioningState->GetFirstVersionNode();
+ while (m_pLinkedListCur != NULL && !ilCodeFilter.IsNull() && ilCodeFilter.GetVersionId() != m_pLinkedListCur->GetILVersionId())
+ {
+ m_pLinkedListCur = m_pLinkedListCur->m_pNextMethodDescSibling;
+ }
+ }
+ if (m_pLinkedListCur != NULL)
+ {
+ m_cur = NativeCodeVersion(m_pLinkedListCur);
+ return;
+ }
+ }
+ if (m_stage == IterationStage::LinkedList)
+ {
+ if (m_pLinkedListCur != NULL)
+ {
+ ILCodeVersion ilCodeFilter = m_pCollection->m_ilCodeFilter;
+ do
+ {
+ m_pLinkedListCur = m_pLinkedListCur->m_pNextMethodDescSibling;
+ } while (m_pLinkedListCur != NULL && !ilCodeFilter.IsNull() && ilCodeFilter.GetVersionId() != m_pLinkedListCur->GetILVersionId());
+ }
+ if (m_pLinkedListCur != NULL)
+ {
+ m_cur = NativeCodeVersion(m_pLinkedListCur);
+ return;
+ }
+ else
+ {
+ m_stage = IterationStage::End;
+ m_cur = NativeCodeVersion();
+ }
+ }
+}
+const NativeCodeVersion & NativeCodeVersionIterator::Get() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ return m_cur;
+}
+bool NativeCodeVersionIterator::Equal(const NativeCodeVersionIterator &i) const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ return m_cur == i.m_cur;
+}
+
+ILCodeVersionNode::ILCodeVersionNode() :
+ m_pModule(dac_cast<PTR_Module>(nullptr)),
+ m_methodDef(0),
+ m_rejitId(0),
+ m_pNextILVersionNode(dac_cast<PTR_ILCodeVersionNode>(nullptr)),
+ m_rejitState(ILCodeVersion::kStateRequested),
+ m_pIL(dac_cast<PTR_COR_ILMETHOD>(nullptr)),
+ m_jitFlags(0)
+{}
+
+#ifndef DACCESS_COMPILE
+ILCodeVersionNode::ILCodeVersionNode(Module* pModule, mdMethodDef methodDef, ReJITID id) :
+ m_pModule(pModule),
+ m_methodDef(methodDef),
+ m_rejitId(id),
+ m_pNextILVersionNode(dac_cast<PTR_ILCodeVersionNode>(nullptr)),
+ m_rejitState(ILCodeVersion::kStateRequested),
+ m_pIL(nullptr),
+ m_jitFlags(0)
+{}
+#endif
+
+#ifdef DEBUG
+BOOL ILCodeVersionNode::LockOwnedByCurrentThread() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ return GetModule()->GetCodeVersionManager()->LockOwnedByCurrentThread();
+}
+#endif //DEBUG
+
+PTR_Module ILCodeVersionNode::GetModule() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ return m_pModule;
+}
+
+mdMethodDef ILCodeVersionNode::GetMethodDef() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ return m_methodDef;
+}
+
+ReJITID ILCodeVersionNode::GetVersionId() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ return m_rejitId;
+}
+
+ILCodeVersion::RejitFlags ILCodeVersionNode::GetRejitState() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ return m_rejitState.Load();
+}
+
+PTR_COR_ILMETHOD ILCodeVersionNode::GetIL() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ return dac_cast<PTR_COR_ILMETHOD>(m_pIL.Load());
+}
+
+DWORD ILCodeVersionNode::GetJitFlags() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ return m_jitFlags.Load();
+}
+
+const InstrumentedILOffsetMapping* ILCodeVersionNode::GetInstrumentedILMap() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ _ASSERTE(LockOwnedByCurrentThread());
+ return &m_instrumentedILMap;
+}
+
+PTR_ILCodeVersionNode ILCodeVersionNode::GetNextILVersionNode() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ _ASSERTE(LockOwnedByCurrentThread());
+ return m_pNextILVersionNode;
+}
+
+#ifndef DACCESS_COMPILE
+void ILCodeVersionNode::SetRejitState(ILCodeVersion::RejitFlags newState)
+{
+ LIMITED_METHOD_CONTRACT;
+ m_rejitState.Store(newState);
+}
+
+void ILCodeVersionNode::SetIL(COR_ILMETHOD* pIL)
+{
+ LIMITED_METHOD_CONTRACT;
+ m_pIL.Store(pIL);
+}
+
+void ILCodeVersionNode::SetJitFlags(DWORD flags)
+{
+ LIMITED_METHOD_CONTRACT;
+ m_jitFlags.Store(flags);
+}
+
+void ILCodeVersionNode::SetInstrumentedILMap(SIZE_T cMap, COR_IL_MAP * rgMap)
+{
+ LIMITED_METHOD_CONTRACT;
+ _ASSERTE(LockOwnedByCurrentThread());
+ m_instrumentedILMap.SetMappingInfo(cMap, rgMap);
+}
+
+void ILCodeVersionNode::SetNextILVersionNode(ILCodeVersionNode* pNextILVersionNode)
+{
+ LIMITED_METHOD_CONTRACT;
+ _ASSERTE(LockOwnedByCurrentThread());
+ m_pNextILVersionNode = pNextILVersionNode;
+}
+#endif
+
+ILCodeVersion::ILCodeVersion() :
+ m_storageKind(StorageKind::Unknown)
+{}
+
+ILCodeVersion::ILCodeVersion(const ILCodeVersion & ilCodeVersion) :
+ m_storageKind(ilCodeVersion.m_storageKind)
+{
+ if(m_storageKind == StorageKind::Explicit)
+ {
+ m_pVersionNode = ilCodeVersion.m_pVersionNode;
+ }
+ else if(m_storageKind == StorageKind::Synthetic)
+ {
+ m_synthetic = ilCodeVersion.m_synthetic;
+ }
+}
+
+ILCodeVersion::ILCodeVersion(PTR_ILCodeVersionNode pILCodeVersionNode) :
+ m_storageKind(pILCodeVersionNode != NULL ? StorageKind::Explicit : StorageKind::Unknown),
+ m_pVersionNode(pILCodeVersionNode)
+{}
+
+ILCodeVersion::ILCodeVersion(PTR_Module pModule, mdMethodDef methodDef) :
+ m_storageKind(pModule != NULL ? StorageKind::Synthetic : StorageKind::Unknown)
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ m_synthetic.m_pModule = pModule;
+ m_synthetic.m_methodDef = methodDef;
+}
+
+bool ILCodeVersion::operator==(const ILCodeVersion & rhs) const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ if (m_storageKind == StorageKind::Explicit)
+ {
+ return (rhs.m_storageKind == StorageKind::Explicit) &&
+ (AsNode() == rhs.AsNode());
+ }
+ else if (m_storageKind == StorageKind::Synthetic)
+ {
+ return (rhs.m_storageKind == StorageKind::Synthetic) &&
+ (m_synthetic.m_pModule == rhs.m_synthetic.m_pModule) &&
+ (m_synthetic.m_methodDef == rhs.m_synthetic.m_methodDef);
+ }
+ else
+ {
+ return rhs.m_storageKind == StorageKind::Unknown;
+ }
+}
+
+BOOL ILCodeVersion::IsNull() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ return m_storageKind == StorageKind::Unknown;
+}
+
+BOOL ILCodeVersion::IsDefaultVersion() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ return m_storageKind == StorageKind::Synthetic;
+}
+
+PTR_Module ILCodeVersion::GetModule() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ if (m_storageKind == StorageKind::Explicit)
+ {
+ return AsNode()->GetModule();
+ }
+ else
+ {
+ return m_synthetic.m_pModule;
+ }
+}
+
+mdMethodDef ILCodeVersion::GetMethodDef() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ if (m_storageKind == StorageKind::Explicit)
+ {
+ return AsNode()->GetMethodDef();
+ }
+ else
+ {
+ return m_synthetic.m_methodDef;
+ }
+}
+
+ReJITID ILCodeVersion::GetVersionId() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ if (m_storageKind == StorageKind::Explicit)
+ {
+ return AsNode()->GetVersionId();
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+NativeCodeVersionCollection ILCodeVersion::GetNativeCodeVersions(PTR_MethodDesc pClosedMethodDesc) const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ return NativeCodeVersionCollection(pClosedMethodDesc, *this);
+}
+
+NativeCodeVersion ILCodeVersion::GetActiveNativeCodeVersion(PTR_MethodDesc pClosedMethodDesc) const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ NativeCodeVersionCollection versions = GetNativeCodeVersions(pClosedMethodDesc);
+ for (NativeCodeVersionIterator cur = versions.Begin(), end = versions.End(); cur != end; cur++)
+ {
+ if (cur->IsActiveChildVersion())
+ {
+ return *cur;
+ }
+ }
+ return NativeCodeVersion();
+}
+
+ILCodeVersion::RejitFlags ILCodeVersion::GetRejitState() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ if (m_storageKind == StorageKind::Explicit)
+ {
+ return AsNode()->GetRejitState();
+ }
+ else
+ {
+ return ILCodeVersion::kStateActive;
+ }
+}
+
+PTR_COR_ILMETHOD ILCodeVersion::GetIL() const
+{
+ CONTRACTL
+ {
+ THROWS; //GetILHeader throws
+ GC_NOTRIGGER;
+ FORBID_FAULT;
+ MODE_ANY;
+ }
+ CONTRACTL_END
+
+ if (m_storageKind == StorageKind::Explicit)
+ {
+ return AsNode()->GetIL();
+ }
+ else
+ {
+ PTR_Module pModule = GetModule();
+ PTR_MethodDesc pMethodDesc = dac_cast<PTR_MethodDesc>(pModule->LookupMethodDef(GetMethodDef()));
+ if (pMethodDesc == NULL)
+ {
+ return NULL;
+ }
+ else
+ {
+ return dac_cast<PTR_COR_ILMETHOD>(pMethodDesc->GetILHeader(TRUE));
+ }
+ }
+}
+
+PTR_COR_ILMETHOD ILCodeVersion::GetILNoThrow() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ PTR_COR_ILMETHOD ret;
+ EX_TRY
+ {
+ ret = GetIL();
+ }
+ EX_CATCH
+ {
+ ret = NULL;
+ }
+ EX_END_CATCH(RethrowTerminalExceptions);
+ return ret;
+}
+
+DWORD ILCodeVersion::GetJitFlags() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ if (m_storageKind == StorageKind::Explicit)
+ {
+ return AsNode()->GetJitFlags();
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+const InstrumentedILOffsetMapping* ILCodeVersion::GetInstrumentedILMap() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ if (m_storageKind == StorageKind::Explicit)
+ {
+ return AsNode()->GetInstrumentedILMap();
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+#ifndef DACCESS_COMPILE
+void ILCodeVersion::SetRejitState(RejitFlags newState)
+{
+ LIMITED_METHOD_CONTRACT;
+ AsNode()->SetRejitState(newState);
+}
+
+void ILCodeVersion::SetIL(COR_ILMETHOD* pIL)
+{
+ LIMITED_METHOD_CONTRACT;
+ AsNode()->SetIL(pIL);
+}
+
+void ILCodeVersion::SetJitFlags(DWORD flags)
+{
+ LIMITED_METHOD_CONTRACT;
+ AsNode()->SetJitFlags(flags);
+}
+
+void ILCodeVersion::SetInstrumentedILMap(SIZE_T cMap, COR_IL_MAP * rgMap)
+{
+ LIMITED_METHOD_CONTRACT;
+ AsNode()->SetInstrumentedILMap(cMap, rgMap);
+}
+
+HRESULT ILCodeVersion::AddNativeCodeVersion(MethodDesc* pClosedMethodDesc, NativeCodeVersion* pNativeCodeVersion)
+{
+ LIMITED_METHOD_CONTRACT;
+ CodeVersionManager* pManager = GetModule()->GetCodeVersionManager();
+ HRESULT hr = pManager->AddNativeCodeVersion(*this, pClosedMethodDesc, pNativeCodeVersion);
+ if (FAILED(hr))
+ {
+ _ASSERTE(hr == E_OUTOFMEMORY);
+ return hr;
+ }
+ return S_OK;
+}
+
+HRESULT ILCodeVersion::GetOrCreateActiveNativeCodeVersion(MethodDesc* pClosedMethodDesc, NativeCodeVersion* pActiveNativeCodeVersion)
+{
+ LIMITED_METHOD_CONTRACT;
+ HRESULT hr = S_OK;
+ NativeCodeVersion activeNativeChild = GetActiveNativeCodeVersion(pClosedMethodDesc);
+ if (activeNativeChild.IsNull())
+ {
+ if (FAILED(hr = AddNativeCodeVersion(pClosedMethodDesc, &activeNativeChild)))
+ {
+ _ASSERTE(hr == E_OUTOFMEMORY);
+ return hr;
+ }
+ }
+ // The first added child should automatically become active
+ _ASSERTE(GetActiveNativeCodeVersion(pClosedMethodDesc) == activeNativeChild);
+ *pActiveNativeCodeVersion = activeNativeChild;
+ return S_OK;
+}
+
+HRESULT ILCodeVersion::SetActiveNativeCodeVersion(NativeCodeVersion activeNativeCodeVersion, BOOL fEESuspended)
+{
+ LIMITED_METHOD_CONTRACT;
+ HRESULT hr = S_OK;
+ MethodDesc* pMethodDesc = activeNativeCodeVersion.GetMethodDesc();
+ NativeCodeVersion prevActiveVersion = GetActiveNativeCodeVersion(pMethodDesc);
+ if (prevActiveVersion == activeNativeCodeVersion)
+ {
+ //nothing to do, this version is already active
+ return S_OK;
+ }
+
+ if (!prevActiveVersion.IsNull())
+ {
+ prevActiveVersion.SetActiveChildFlag(FALSE);
+ }
+ activeNativeCodeVersion.SetActiveChildFlag(TRUE);
+
+ // If needed update the published code body for this method
+ CodeVersionManager* pCodeVersionManager = GetModule()->GetCodeVersionManager();
+ if (pCodeVersionManager->GetActiveILCodeVersion(GetModule(), GetMethodDef()) == *this)
+ {
+ if (FAILED(hr = pCodeVersionManager->PublishNativeCodeVersion(pMethodDesc, activeNativeCodeVersion, fEESuspended)))
+ {
+ return hr;
+ }
+ }
+
+ return S_OK;
+}
+
+ILCodeVersionNode* ILCodeVersion::AsNode()
+{
+ LIMITED_METHOD_CONTRACT;
+ return m_pVersionNode;
+}
+#endif //DACCESS_COMPILE
+
+PTR_ILCodeVersionNode ILCodeVersion::AsNode() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ return m_pVersionNode;
+}
+
+ILCodeVersionCollection::ILCodeVersionCollection(PTR_Module pModule, mdMethodDef methodDef) :
+ m_pModule(pModule),
+ m_methodDef(methodDef)
+{}
+
+ILCodeVersionIterator ILCodeVersionCollection::Begin()
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ return ILCodeVersionIterator(this);
+}
+
+ILCodeVersionIterator ILCodeVersionCollection::End()
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ return ILCodeVersionIterator(NULL);
+}
+
+ILCodeVersionIterator::ILCodeVersionIterator(const ILCodeVersionIterator & iter) :
+ m_stage(iter.m_stage),
+ m_cur(iter.m_cur),
+ m_pLinkedListCur(iter.m_pLinkedListCur),
+ m_pCollection(iter.m_pCollection)
+{}
+
+ILCodeVersionIterator::ILCodeVersionIterator(ILCodeVersionCollection* pCollection) :
+ m_stage(pCollection != NULL ? IterationStage::Initial : IterationStage::End),
+ m_pLinkedListCur(dac_cast<PTR_ILCodeVersionNode>(nullptr)),
+ m_pCollection(pCollection)
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ First();
+}
+
+const ILCodeVersion & ILCodeVersionIterator::Get() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ return m_cur;
+}
+
+void ILCodeVersionIterator::First()
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ Next();
+}
+
+void ILCodeVersionIterator::Next()
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ if (m_stage == IterationStage::Initial)
+ {
+ m_stage = IterationStage::ImplicitCodeVersion;
+ m_cur = ILCodeVersion(m_pCollection->m_pModule, m_pCollection->m_methodDef);
+ return;
+ }
+ if (m_stage == IterationStage::ImplicitCodeVersion)
+ {
+ CodeVersionManager* pCodeVersionManager = m_pCollection->m_pModule->GetCodeVersionManager();
+ _ASSERTE(pCodeVersionManager->LockOwnedByCurrentThread());
+ PTR_ILCodeVersioningState pILCodeVersioningState = pCodeVersionManager->GetILCodeVersioningState(m_pCollection->m_pModule, m_pCollection->m_methodDef);
+ if (pILCodeVersioningState != NULL)
+ {
+ m_pLinkedListCur = pILCodeVersioningState->GetFirstVersionNode();
+ }
+ m_stage = IterationStage::LinkedList;
+ if (m_pLinkedListCur != NULL)
+ {
+ m_cur = ILCodeVersion(m_pLinkedListCur);
+ return;
+ }
+ }
+ if (m_stage == IterationStage::LinkedList)
+ {
+ if (m_pLinkedListCur != NULL)
+ {
+ m_pLinkedListCur = m_pLinkedListCur->GetNextILVersionNode();
+ }
+ if (m_pLinkedListCur != NULL)
+ {
+ m_cur = ILCodeVersion(m_pLinkedListCur);
+ return;
+ }
+ else
+ {
+ m_stage = IterationStage::End;
+ m_cur = ILCodeVersion();
+ return;
+ }
+ }
+}
+
+bool ILCodeVersionIterator::Equal(const ILCodeVersionIterator &i) const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ return m_cur == i.m_cur;
+}
+
+MethodDescVersioningState::MethodDescVersioningState(PTR_MethodDesc pMethodDesc) :
+ m_pMethodDesc(pMethodDesc),
+ m_flags(IsDefaultVersionActiveChildFlag),
+ m_nextId(1),
+ m_pFirstVersionNode(dac_cast<PTR_NativeCodeVersionNode>(nullptr))
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+#ifdef FEATURE_JUMPSTAMP
+ ZeroMemory(m_rgSavedCode, JumpStubSize);
+#endif
+}
+
+PTR_MethodDesc MethodDescVersioningState::GetMethodDesc() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ return m_pMethodDesc;
+}
+
+#ifndef DACCESS_COMPILE
+NativeCodeVersionId MethodDescVersioningState::AllocateVersionId()
+{
+ LIMITED_METHOD_CONTRACT;
+ return m_nextId++;
+}
+#endif
+
+PTR_NativeCodeVersionNode MethodDescVersioningState::GetFirstVersionNode() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ return m_pFirstVersionNode;
+}
+
+#ifdef FEATURE_JUMPSTAMP
+MethodDescVersioningState::JumpStampFlags MethodDescVersioningState::GetJumpStampState()
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ return (JumpStampFlags)(m_flags & JumpStampMask);
+}
+
+#ifndef DACCESS_COMPILE
+void MethodDescVersioningState::SetJumpStampState(JumpStampFlags newState)
+{
+ LIMITED_METHOD_CONTRACT;
+ m_flags = (m_flags & ~JumpStampMask) | (BYTE)newState;
+}
+#endif // DACCESS_COMPILE
+
+#ifndef DACCESS_COMPILE
+HRESULT MethodDescVersioningState::SyncJumpStamp(NativeCodeVersion nativeCodeVersion, BOOL fEESuspended)
+ {
+ LIMITED_METHOD_CONTRACT;
+ HRESULT hr = S_OK;
+ PCODE pCode = nativeCodeVersion.IsNull() ? NULL : nativeCodeVersion.GetNativeCode();
+ MethodDesc* pMethod = GetMethodDesc();
+ _ASSERTE(pMethod->IsVersionable() && pMethod->IsVersionableWithJumpStamp());
+
+ if (!pMethod->HasNativeCode())
+ {
+ //we'll set up the jump-stamp when the default native code is created
+ return S_OK;
+ }
+
+ if (!nativeCodeVersion.IsNull() && nativeCodeVersion.IsDefaultVersion())
+ {
+ return UndoJumpStampNativeCode(fEESuspended);
+ }
+ else
+ {
+ // We don't have new code ready yet, jumpstamp back to the prestub to let us generate it the next time
+ // the method is called
+ if (pCode == NULL)
+ {
+ if (!fEESuspended)
+ {
+ return CORPROF_E_RUNTIME_SUSPEND_REQUIRED;
+ }
+ return JumpStampNativeCode();
+ }
+ // We do know the new code body, install the jump stamp now
+ else
+ {
+ return UpdateJumpTarget(fEESuspended, pCode);
+ }
+ }
+}
+#endif // DACCESS_COMPILE
+
+//---------------------------------------------------------------------------------------
+//
+// Simple, thin abstraction of debugger breakpoint patching. Given an address and a
+// previously procured DebuggerControllerPatch governing the code address, this decides
+// whether the code address is patched. If so, it returns a pointer to the debugger's
+// buffer (of what's "underneath" the int 3 patch); otherwise, it returns the code
+// address itself.
+//
+// Arguments:
+// * pbCode - Code address to return if unpatched
+// * dbgpatch - DebuggerControllerPatch to test
+//
+// Return Value:
+// Either pbCode or the debugger's patch buffer, as per description above.
+//
+// Assumptions:
+// Caller must manually grab (and hold) the ControllerLockHolder and get the
+// DebuggerControllerPatch before calling this helper.
+//
+// Notes:
+// pbCode need not equal the code address governed by dbgpatch, but is always
+// "related" (and sometimes really is equal). For example, this helper may be used
+// when writing a code byte to an internal rejit buffer (e.g., in preparation for an
+// eventual 64-bit interlocked write into the code stream), and thus pbCode would
+// point into the internal rejit buffer whereas dbgpatch governs the corresponding
+// code byte in the live code stream. This function would then be used to determine
+// whether a byte should be written into the internal rejit buffer OR into the
+// debugger controller's breakpoint buffer.
+//
+
+LPBYTE FirstCodeByteAddr(LPBYTE pbCode, DebuggerControllerPatch * dbgpatch)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ if (dbgpatch != NULL && dbgpatch->IsActivated())
+ {
+ // Debugger has patched the code, so return the address of the buffer
+ return LPBYTE(&(dbgpatch->opcode));
+ }
+
+ // no active patch, just return the direct code address
+ return pbCode;
+}
+
+
+#ifdef _DEBUG
+#ifndef DACCESS_COMPILE
+BOOL MethodDescVersioningState::CodeIsSaved()
+{
+ LIMITED_METHOD_CONTRACT;
+
+ for (size_t i = 0; i < sizeof(m_rgSavedCode); i++)
+ {
+ if (m_rgSavedCode[i] != 0)
+ return TRUE;
+ }
+ return FALSE;
+}
+#endif //DACCESS_COMPILE
+#endif //_DEBUG
+
+//---------------------------------------------------------------------------------------
+//
+// Do the actual work of stamping the top of originally-jitted-code with a jmp that goes
+// to the prestub. This can be called in one of three ways:
+// * Case 1: By RequestReJIT against an already-jitted function, in which case the
+// PCODE may be inferred by the MethodDesc, and our caller will have suspended
+// the EE for us, OR
+// * Case 2: By the prestub worker after jitting the original code of a function
+// (i.e., the "pre-rejit" scenario). In this case, the EE is not suspended. But
+// that's ok, because the PCODE has not yet been published to the MethodDesc, and
+// no thread can be executing inside the originally JITted function yet.
+// * Case 3: At type/method restore time for an NGEN'ed assembly. This is also the pre-rejit
+// scenario because we are guaranteed to do this before the code in the module
+// is executable. EE suspend is not required.
+//
+// Arguments:
+// * pCode - Case 1 (above): will be NULL, and we can infer the PCODE from the
+// MethodDesc; Case 2+3 (above, pre-rejit): will be non-NULL, and we'll need to use
+// this to find the code to stamp on top of.
+//
+// Return Value:
+// * S_OK: Either we successfully did the jmp-stamp, or a racing thread took care of
+// it for us.
+// * Else, HRESULT indicating failure.
+//
+// Assumptions:
+// The caller will have suspended the EE if necessary (case 1), before this is
+// called.
+//
+#ifndef DACCESS_COMPILE
+HRESULT MethodDescVersioningState::JumpStampNativeCode(PCODE pCode /* = NULL */)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ // It may seem dangerous to be stamping jumps over code while a GC is going on,
+ // but we're actually safe. As we assert below, either we're holding the thread
+ // store lock (and thus preventing a GC) OR we're stamping code that has not yet
+ // been published (and will thus not be executed by managed therads or examined
+ // by the GC).
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ PCODE pCodePublished = GetMethodDesc()->GetNativeCode();
+
+ _ASSERTE((pCode != NULL) || (pCodePublished != NULL));
+ _ASSERTE(GetMethodDesc()->GetCodeVersionManager()->LockOwnedByCurrentThread());
+
+ HRESULT hr = S_OK;
+
+ // We'll jump-stamp over pCode, or if pCode is NULL, jump-stamp over the published
+ // code for this's MethodDesc.
+ LPBYTE pbCode = (LPBYTE)pCode;
+ if (pbCode == NULL)
+ {
+ // If caller didn't specify a pCode, just use the one that was published after
+ // the original JIT. (A specific pCode would be passed in the pre-rejit case,
+ // to jump-stamp the original code BEFORE the PCODE gets published.)
+ pbCode = (LPBYTE)pCodePublished;
+ }
+ _ASSERTE(pbCode != NULL);
+
+ // The debugging API may also try to write to the very top of this function (though
+ // with an int 3 for breakpoint purposes). Coordinate with the debugger so we know
+ // whether we can safely patch the actual code, or instead write to the debugger's
+ // buffer.
+ DebuggerController::ControllerLockHolder lockController;
+
+ if (GetJumpStampState() == JumpStampToPrestub)
+ {
+ // The method has already been jump stamped so nothing left to do
+ _ASSERTE(CodeIsSaved());
+ return S_OK;
+ }
+
+ // Remember what we're stamping our jump on top of, so we can replace it during a
+ // revert.
+ if (GetJumpStampState() == JumpStampNone)
+ {
+ for (int i = 0; i < sizeof(m_rgSavedCode); i++)
+ {
+ m_rgSavedCode[i] = *FirstCodeByteAddr(pbCode + i, DebuggerController::GetPatchTable()->GetPatch((CORDB_ADDRESS_TYPE *)(pbCode + i)));
+ }
+ }
+
+ EX_TRY
+ {
+ AllocMemTracker amt;
+
+ // This guy might throw on out-of-memory, so rely on the tracker to clean-up
+ Precode * pPrecode = Precode::Allocate(PRECODE_STUB, GetMethodDesc(), GetMethodDesc()->GetLoaderAllocator(), &amt);
+ PCODE target = pPrecode->GetEntryPoint();
+
+#if defined(_X86_) || defined(_AMD64_)
+
+ // Normal unpatched code never starts with a jump
+ _ASSERTE(GetJumpStampState() == JumpStampToActiveVersion ||
+ *FirstCodeByteAddr(pbCode, DebuggerController::GetPatchTable()->GetPatch((CORDB_ADDRESS_TYPE *)pbCode)) != X86_INSTR_JMP_REL32);
+
+ INT64 i64OldCode = *(INT64*)pbCode;
+ INT64 i64NewCode = i64OldCode;
+ LPBYTE pbNewValue = (LPBYTE)&i64NewCode;
+ *pbNewValue = X86_INSTR_JMP_REL32;
+ INT32 UNALIGNED * pOffset = reinterpret_cast<INT32 UNALIGNED *>(&pbNewValue[1]);
+ // This will throw for out-of-memory, so don't write anything until
+ // after he succeeds
+ // This guy will leak/cache/reuse the jumpstub
+ *pOffset = rel32UsingJumpStub(reinterpret_cast<INT32 UNALIGNED *>(pbCode + 1), target, GetMethodDesc(), GetMethodDesc()->GetLoaderAllocator());
+
+ // If we have the EE suspended or the code is unpublished there won't be contention on this code
+ hr = UpdateJumpStampHelper(pbCode, i64OldCode, i64NewCode, FALSE);
+ if (FAILED(hr))
+ {
+ ThrowHR(hr);
+ }
+
+ //
+ // No failure point after this!
+ //
+ amt.SuppressRelease();
+
+#else // _X86_ || _AMD64_
+#error "Need to define a way to jump-stamp the prolog in a safe way for this platform"
+#endif // _X86_ || _AMD64_
+
+ SetJumpStampState(JumpStampToPrestub);
+ }
+ EX_CATCH_HRESULT(hr);
+ _ASSERT(hr == S_OK || hr == E_OUTOFMEMORY);
+
+ if (SUCCEEDED(hr))
+ {
+ _ASSERTE(GetJumpStampState() == JumpStampToPrestub);
+ _ASSERTE(m_rgSavedCode[0] != 0); // saved code should not start with 0
+ }
+
+ return hr;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// After code has been rejitted, this is called to update the jump-stamp to go from
+// pointing to the prestub, to pointing to the newly rejitted code.
+//
+// Arguments:
+// fEESuspended - TRUE if the caller keeps the EE suspended during this call
+// pRejittedCode - jitted code for the updated IL this method should execute
+//
+// Assumptions:
+// This rejit manager's table crst should be held by the caller
+//
+// Returns - S_OK if the jump target is updated
+// CORPROF_E_RUNTIME_SUSPEND_REQUIRED if the ee isn't suspended and it
+// will need to be in order to do the update safely
+HRESULT MethodDescVersioningState::UpdateJumpTarget(BOOL fEESuspended, PCODE pRejittedCode)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_PREEMPTIVE;
+ }
+ CONTRACTL_END;
+
+ MethodDesc * pMD = GetMethodDesc();
+ _ASSERTE(pMD->GetCodeVersionManager()->LockOwnedByCurrentThread());
+
+ // It isn't safe to overwrite the original method prolog with a jmp because threads might
+ // be at an IP in the middle of the jump stamp already. However converting between different
+ // jump stamps is OK (when done atomically) because this only changes the jmp target, not
+ // instruction boundaries.
+ if (GetJumpStampState() == JumpStampNone && !fEESuspended)
+ {
+ return CORPROF_E_RUNTIME_SUSPEND_REQUIRED;
+ }
+
+ // Beginning of originally JITted code containing the jmp that we will redirect.
+ BYTE * pbCode = (BYTE*)pMD->GetNativeCode();
+
+ // Remember what we're stamping our jump on top of, so we can replace it during a
+ // revert.
+ if (GetJumpStampState() == JumpStampNone)
+ {
+ for (int i = 0; i < sizeof(m_rgSavedCode); i++)
+ {
+ m_rgSavedCode[i] = *FirstCodeByteAddr(pbCode + i, DebuggerController::GetPatchTable()->GetPatch((CORDB_ADDRESS_TYPE *)(pbCode + i)));
+ }
+ }
+
+#if defined(_X86_) || defined(_AMD64_)
+
+ HRESULT hr = S_OK;
+ {
+ DebuggerController::ControllerLockHolder lockController;
+
+ // This will throw for out-of-memory, so don't write anything until
+ // after he succeeds
+ // This guy will leak/cache/reuse the jumpstub
+ INT32 offset = 0;
+ EX_TRY
+ {
+ offset = rel32UsingJumpStub(
+ reinterpret_cast<INT32 UNALIGNED *>(&pbCode[1]), // base of offset
+ pRejittedCode, // target of jump
+ pMD,
+ pMD->GetLoaderAllocator());
+ }
+ EX_CATCH_HRESULT(hr);
+ _ASSERT(hr == S_OK || hr == E_OUTOFMEMORY);
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+ // For validation later, remember what pbCode is right now
+ INT64 i64OldValue = *(INT64 *)pbCode;
+
+ // Assemble the INT64 of the new code bytes to write. Start with what's there now
+ INT64 i64NewValue = i64OldValue;
+ LPBYTE pbNewValue = (LPBYTE)&i64NewValue;
+
+ // First byte becomes a rel32 jmp instruction (if it wasn't already)
+ *pbNewValue = X86_INSTR_JMP_REL32;
+ // Next 4 bytes are the jmp target (offset to jmp stub)
+ INT32 UNALIGNED * pnOffset = reinterpret_cast<INT32 UNALIGNED *>(&pbNewValue[1]);
+ *pnOffset = offset;
+
+ hr = UpdateJumpStampHelper(pbCode, i64OldValue, i64NewValue, !fEESuspended);
+ _ASSERTE(hr == S_OK || (hr == CORPROF_E_RUNTIME_SUSPEND_REQUIRED && !fEESuspended));
+ }
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+#else // _X86_ || _AMD64_
+#error "Need to define a way to jump-stamp the prolog in a safe way for this platform"
+#endif // _X86_ || _AMD64_
+
+ // State transition
+ SetJumpStampState(JumpStampToActiveVersion);
+ return S_OK;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Poke the JITted code to satsify a revert request (or to perform an implicit revert as
+// part of a second, third, etc. rejit request). Reinstates the originally JITted code
+// that had been jump-stamped over to perform a prior rejit.
+//
+// Arguments
+// fEESuspended - TRUE if the caller keeps the EE suspended during this call
+//
+//
+// Return Value:
+// S_OK to indicate the revert succeeded,
+// CORPROF_E_RUNTIME_SUSPEND_REQUIRED to indicate the jumpstamp hasn't been reverted
+// and EE suspension will be needed for success
+// other failure HRESULT indicating what went wrong.
+//
+// Assumptions:
+// Caller must be holding the owning ReJitManager's table crst.
+//
+HRESULT MethodDescVersioningState::UndoJumpStampNativeCode(BOOL fEESuspended)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(GetMethodDesc()->GetCodeVersionManager()->LockOwnedByCurrentThread());
+ if (GetJumpStampState() == JumpStampNone)
+ {
+ return S_OK;
+ }
+
+ _ASSERTE(m_rgSavedCode[0] != 0); // saved code should not start with 0
+
+ BYTE * pbCode = (BYTE*)GetMethodDesc()->GetNativeCode();
+ DebuggerController::ControllerLockHolder lockController;
+
+#if defined(_X86_) || defined(_AMD64_)
+ _ASSERTE(m_rgSavedCode[0] != X86_INSTR_JMP_REL32);
+ _ASSERTE(*FirstCodeByteAddr(pbCode, DebuggerController::GetPatchTable()->GetPatch((CORDB_ADDRESS_TYPE *)pbCode)) == X86_INSTR_JMP_REL32);
+#else
+#error "Need to define a way to jump-stamp the prolog in a safe way for this platform"
+#endif // _X86_ || _AMD64_
+
+ // For the interlocked compare, remember what pbCode is right now
+ INT64 i64OldValue = *(INT64 *)pbCode;
+ // Assemble the INT64 of the new code bytes to write. Start with what's there now
+ INT64 i64NewValue = i64OldValue;
+ memcpy(LPBYTE(&i64NewValue), m_rgSavedCode, sizeof(m_rgSavedCode));
+ HRESULT hr = UpdateJumpStampHelper(pbCode, i64OldValue, i64NewValue, !fEESuspended);
+ _ASSERTE(hr == S_OK || (hr == CORPROF_E_RUNTIME_SUSPEND_REQUIRED && !fEESuspended));
+ if (hr != S_OK)
+ return hr;
+
+ // Transition state of this ReJitInfo to indicate the MD no longer has any jump stamp
+ SetJumpStampState(JumpStampNone);
+ return S_OK;
+}
+#endif
+
+//---------------------------------------------------------------------------------------
+//
+// This is called to modify the jump-stamp area, the first ReJitInfo::JumpStubSize bytes
+// in the method's code.
+//
+// Notes:
+// Callers use this method in a variety of circumstances:
+// a) when the code is unpublished (fContentionPossible == FALSE)
+// b) when the caller has taken the ThreadStoreLock and suspended the EE
+// (fContentionPossible == FALSE)
+// c) when the code is published, the EE isn't suspended, and the jumpstamp
+// area consists of a single 5 byte long jump instruction
+// (fContentionPossible == TRUE)
+// This method will attempt to alter the jump-stamp even if the caller has not prevented
+// contention, but there is no guarantee it will be succesful. When the caller has prevented
+// contention, then success is assured. Callers may oportunistically try without
+// EE suspension, and then upgrade to EE suspension if the first attempt fails.
+//
+// Assumptions:
+// This rejit manager's table crst should be held by the caller or fContentionPossible==FALSE
+// The debugger patch table lock should be held by the caller
+//
+// Arguments:
+// pbCode - pointer to the code where the jump stamp is placed
+// i64OldValue - the bytes which should currently be at the start of the method code
+// i64NewValue - the new bytes which should be written at the start of the method code
+// fContentionPossible - See the Notes section above.
+//
+// Returns:
+// S_OK => the jumpstamp has been succesfully updated.
+// CORPROF_E_RUNTIME_SUSPEND_REQUIRED => the jumpstamp remains unchanged (preventing contention will be necessary)
+// other failing HR => VirtualProtect failed, the jumpstamp remains unchanged
+//
+#ifndef DACCESS_COMPILE
+HRESULT MethodDescVersioningState::UpdateJumpStampHelper(BYTE* pbCode, INT64 i64OldValue, INT64 i64NewValue, BOOL fContentionPossible)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ MethodDesc * pMD = GetMethodDesc();
+ _ASSERTE(pMD->GetCodeVersionManager()->LockOwnedByCurrentThread() || !fContentionPossible);
+
+ // When ReJIT is enabled, method entrypoints are always at least 8-byte aligned (see
+ // code:EEJitManager::allocCode), so we can do a single 64-bit interlocked operation
+ // to update the jump target. However, some code may have gotten compiled before
+ // the profiler had a chance to enable ReJIT (e.g., NGENd code, or code JITted
+ // before a profiler attaches). In such cases, we cannot rely on a simple
+ // interlocked operation, and instead must suspend the runtime to ensure we can
+ // safely update the jmp instruction.
+ //
+ // This method doesn't verify that the method is actually safe to rejit, we expect
+ // callers to do that. At the moment NGEN'ed code is safe to rejit even if
+ // it is unaligned, but code generated before the profiler attaches is not.
+ if (fContentionPossible && !(IS_ALIGNED(pbCode, sizeof(INT64))))
+ {
+ return CORPROF_E_RUNTIME_SUSPEND_REQUIRED;
+ }
+
+ // The debugging API may also try to write to this function (though
+ // with an int 3 for breakpoint purposes). Coordinate with the debugger so we know
+ // whether we can safely patch the actual code, or instead write to the debugger's
+ // buffer.
+ if (fContentionPossible)
+ {
+ for (CORDB_ADDRESS_TYPE* pbProbeAddr = pbCode; pbProbeAddr < pbCode + MethodDescVersioningState::JumpStubSize; pbProbeAddr++)
+ {
+ if (NULL != DebuggerController::GetPatchTable()->GetPatch(pbProbeAddr))
+ {
+ return CORPROF_E_RUNTIME_SUSPEND_REQUIRED;
+ }
+ }
+ }
+
+#if defined(_X86_) || defined(_AMD64_)
+
+ DWORD oldProt;
+ if (!ClrVirtualProtect((LPVOID)pbCode, 8, PAGE_EXECUTE_READWRITE, &oldProt))
+ {
+ return HRESULT_FROM_WIN32(GetLastError());
+ }
+
+ if (fContentionPossible)
+ {
+ INT64 i64InterlockReportedOldValue = FastInterlockCompareExchangeLong((INT64 *)pbCode, i64NewValue, i64OldValue);
+ // Since changes to these bytes are protected by this rejitmgr's m_crstTable, we
+ // shouldn't have two writers conflicting.
+ _ASSERTE(i64InterlockReportedOldValue == i64OldValue);
+ }
+ else
+ {
+ // In this path the caller ensures:
+ // a) no thread will execute through the prologue area we are modifying
+ // b) no thread is stopped in a prologue such that it resumes in the middle of code we are modifying
+ // c) no thread is doing a debugger patch skip operation in which an unmodified copy of the method's
+ // code could be executed from a patch skip buffer.
+
+ // PERF: we might still want a faster path through here if we aren't debugging that doesn't do
+ // all the patch checks
+ for (int i = 0; i < MethodDescVersioningState::JumpStubSize; i++)
+ {
+ *FirstCodeByteAddr(pbCode + i, DebuggerController::GetPatchTable()->GetPatch(pbCode + i)) = ((BYTE*)&i64NewValue)[i];
+ }
+ }
+
+ if (oldProt != PAGE_EXECUTE_READWRITE)
+ {
+ // The CLR codebase in many locations simply ignores failures to restore the page protections
+ // Its true that it isn't a problem functionally, but it seems a bit sketchy?
+ // I am following the convention for now.
+ ClrVirtualProtect((LPVOID)pbCode, 8, oldProt, &oldProt);
+ }
+
+ FlushInstructionCache(GetCurrentProcess(), pbCode, MethodDescVersioningState::JumpStubSize);
+ return S_OK;
+
+#else // _X86_ || _AMD64_
+#error "Need to define a way to jump-stamp the prolog in a safe way for this platform"
+#endif // _X86_ || _AMD64_
+}
+#endif
+#endif // FEATURE_JUMPSTAMP
+
+BOOL MethodDescVersioningState::IsDefaultVersionActiveChild() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ return (m_flags & IsDefaultVersionActiveChildFlag) != 0;
+}
+#ifndef DACCESS_COMPILE
+void MethodDescVersioningState::SetDefaultVersionActiveChildFlag(BOOL isActive)
+{
+ LIMITED_METHOD_CONTRACT;
+ if (isActive)
+ {
+ m_flags |= IsDefaultVersionActiveChildFlag;
+ }
+ else
+ {
+ m_flags &= ~IsDefaultVersionActiveChildFlag;
+ }
+}
+
+void MethodDescVersioningState::LinkNativeCodeVersionNode(NativeCodeVersionNode* pNativeCodeVersionNode)
+{
+ LIMITED_METHOD_CONTRACT;
+ pNativeCodeVersionNode->m_pNextMethodDescSibling = m_pFirstVersionNode;
+ m_pFirstVersionNode = pNativeCodeVersionNode;
+}
+#endif
+
+ILCodeVersioningState::ILCodeVersioningState(PTR_Module pModule, mdMethodDef methodDef) :
+ m_activeVersion(ILCodeVersion(pModule,methodDef)),
+ m_pFirstVersionNode(dac_cast<PTR_ILCodeVersionNode>(nullptr)),
+ m_pModule(pModule),
+ m_methodDef(methodDef)
+{}
+
+
+ILCodeVersioningState::Key::Key() :
+ m_pModule(dac_cast<PTR_Module>(nullptr)),
+ m_methodDef(0)
+{}
+
+ILCodeVersioningState::Key::Key(PTR_Module pModule, mdMethodDef methodDef) :
+ m_pModule(pModule),
+ m_methodDef(methodDef)
+{}
+
+size_t ILCodeVersioningState::Key::Hash() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ return (size_t)(dac_cast<TADDR>(m_pModule) ^ m_methodDef);
+}
+
+bool ILCodeVersioningState::Key::operator==(const Key & rhs) const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ return (m_pModule == rhs.m_pModule) && (m_methodDef == rhs.m_methodDef);
+}
+
+ILCodeVersioningState::Key ILCodeVersioningState::GetKey() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ return Key(m_pModule, m_methodDef);
+}
+
+ILCodeVersion ILCodeVersioningState::GetActiveVersion() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ return m_activeVersion;
+}
+
+PTR_ILCodeVersionNode ILCodeVersioningState::GetFirstVersionNode() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ return m_pFirstVersionNode;
+}
+
+#ifndef DACCESS_COMPILE
+void ILCodeVersioningState::SetActiveVersion(ILCodeVersion ilActiveCodeVersion)
+{
+ LIMITED_METHOD_CONTRACT;
+ m_activeVersion = ilActiveCodeVersion;
+}
+
+void ILCodeVersioningState::LinkILCodeVersionNode(ILCodeVersionNode* pILCodeVersionNode)
+{
+ LIMITED_METHOD_CONTRACT;
+ pILCodeVersionNode->SetNextILVersionNode(m_pFirstVersionNode);
+ m_pFirstVersionNode = pILCodeVersionNode;
+}
+#endif
+
+CodeVersionManager::CodeVersionManager()
+{}
+
+//---------------------------------------------------------------------------------------
+//
+// Called from BaseDomain::BaseDomain to do any constructor-time initialization.
+// Presently, this takes care of initializing the Crst, choosing the type based on
+// whether this ReJitManager belongs to the SharedDomain.
+//
+// Arguments:
+// * fSharedDomain - nonzero iff this ReJitManager belongs to the SharedDomain.
+//
+
+void CodeVersionManager::PreInit(BOOL fSharedDomain)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ CAN_TAKE_LOCK;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+#ifndef DACCESS_COMPILE
+ m_crstTable.Init(
+ fSharedDomain ? CrstReJITSharedDomainTable : CrstReJITDomainTable,
+ CrstFlags(CRST_UNSAFE_ANYMODE | CRST_DEBUGGER_THREAD | CRST_REENTRANCY | CRST_TAKEN_DURING_SHUTDOWN));
+#endif // DACCESS_COMPILE
+}
+
+CodeVersionManager::TableLockHolder::TableLockHolder(CodeVersionManager* pCodeVersionManager) :
+ CrstHolder(&pCodeVersionManager->m_crstTable)
+{
+}
+#ifndef DACCESS_COMPILE
+void CodeVersionManager::EnterLock()
+{
+ m_crstTable.Enter();
+}
+void CodeVersionManager::LeaveLock()
+{
+ m_crstTable.Leave();
+}
+#endif
+
+#ifdef DEBUG
+BOOL CodeVersionManager::LockOwnedByCurrentThread() const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+#ifdef DACCESS_COMPILE
+ return TRUE;
+#else
+ return const_cast<CrstExplicitInit &>(m_crstTable).OwnedByCurrentThread();
+#endif
+}
+#endif
+
+PTR_ILCodeVersioningState CodeVersionManager::GetILCodeVersioningState(PTR_Module pModule, mdMethodDef methodDef) const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ ILCodeVersioningState::Key key = ILCodeVersioningState::Key(pModule, methodDef);
+ return m_ilCodeVersioningStateMap.Lookup(key);
+}
+
+PTR_MethodDescVersioningState CodeVersionManager::GetMethodDescVersioningState(PTR_MethodDesc pClosedMethodDesc) const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ return m_methodDescVersioningStateMap.Lookup(pClosedMethodDesc);
+}
+
+#ifndef DACCESS_COMPILE
+HRESULT CodeVersionManager::GetOrCreateILCodeVersioningState(Module* pModule, mdMethodDef methodDef, ILCodeVersioningState** ppILCodeVersioningState)
+{
+ LIMITED_METHOD_CONTRACT;
+ HRESULT hr = S_OK;
+ ILCodeVersioningState* pILCodeVersioningState = GetILCodeVersioningState(pModule, methodDef);
+ if (pILCodeVersioningState == NULL)
+ {
+ pILCodeVersioningState = new (nothrow) ILCodeVersioningState(pModule, methodDef);
+ if (pILCodeVersioningState == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+ EX_TRY
+ {
+ // This throws when out of memory, but remains internally
+ // consistent (without adding the new element)
+ m_ilCodeVersioningStateMap.Add(pILCodeVersioningState);
+ }
+ EX_CATCH_HRESULT(hr);
+ if (FAILED(hr))
+ {
+ delete pILCodeVersioningState;
+ return hr;
+ }
+ }
+ *ppILCodeVersioningState = pILCodeVersioningState;
+ return S_OK;
+}
+
+HRESULT CodeVersionManager::GetOrCreateMethodDescVersioningState(MethodDesc* pMethod, MethodDescVersioningState** ppMethodVersioningState)
+{
+ LIMITED_METHOD_CONTRACT;
+ HRESULT hr = S_OK;
+ MethodDescVersioningState* pMethodVersioningState = m_methodDescVersioningStateMap.Lookup(pMethod);
+ if (pMethodVersioningState == NULL)
+ {
+ pMethodVersioningState = new (nothrow) MethodDescVersioningState(pMethod);
+ if (pMethodVersioningState == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+ EX_TRY
+ {
+ // This throws when out of memory, but remains internally
+ // consistent (without adding the new element)
+ m_methodDescVersioningStateMap.Add(pMethodVersioningState);
+ }
+ EX_CATCH_HRESULT(hr);
+ if (FAILED(hr))
+ {
+ delete pMethodVersioningState;
+ return hr;
+ }
+ }
+ *ppMethodVersioningState = pMethodVersioningState;
+ return S_OK;
+}
+#endif // DACCESS_COMPILE
+
+DWORD CodeVersionManager::GetNonDefaultILVersionCount()
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+
+ //This function is legal to call WITHOUT taking the lock
+ //It is used to do a quick check if work might be needed without paying the overhead
+ //of acquiring the lock and doing dictionary lookups
+ return m_ilCodeVersioningStateMap.GetCount();
+}
+
+ILCodeVersionCollection CodeVersionManager::GetILCodeVersions(PTR_MethodDesc pMethod)
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ _ASSERTE(LockOwnedByCurrentThread());
+ return GetILCodeVersions(dac_cast<PTR_Module>(pMethod->GetModule()), pMethod->GetMemberDef());
+}
+
+ILCodeVersionCollection CodeVersionManager::GetILCodeVersions(PTR_Module pModule, mdMethodDef methodDef)
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ _ASSERTE(LockOwnedByCurrentThread());
+ return ILCodeVersionCollection(pModule, methodDef);
+}
+
+ILCodeVersion CodeVersionManager::GetActiveILCodeVersion(PTR_MethodDesc pMethod)
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ _ASSERTE(LockOwnedByCurrentThread());
+ return GetActiveILCodeVersion(dac_cast<PTR_Module>(pMethod->GetModule()), pMethod->GetMemberDef());
+}
+
+ILCodeVersion CodeVersionManager::GetActiveILCodeVersion(PTR_Module pModule, mdMethodDef methodDef)
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ _ASSERTE(LockOwnedByCurrentThread());
+ ILCodeVersioningState* pILCodeVersioningState = GetILCodeVersioningState(pModule, methodDef);
+ if (pILCodeVersioningState == NULL)
+ {
+ return ILCodeVersion(pModule, methodDef);
+ }
+ else
+ {
+ return pILCodeVersioningState->GetActiveVersion();
+ }
+}
+
+ILCodeVersion CodeVersionManager::GetILCodeVersion(PTR_MethodDesc pMethod, ReJITID rejitId)
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ _ASSERTE(LockOwnedByCurrentThread());
+
+#ifdef FEATURE_REJIT
+ ILCodeVersionCollection collection = GetILCodeVersions(pMethod);
+ for (ILCodeVersionIterator cur = collection.Begin(), end = collection.End(); cur != end; cur++)
+ {
+ if (cur->GetVersionId() == rejitId)
+ {
+ return *cur;
+ }
+ }
+ return ILCodeVersion();
+#else // FEATURE_REJIT
+ _ASSERTE(rejitId == 0);
+ return ILCodeVersion(dac_cast<PTR_Module>(pMethod->GetModule()), pMethod->GetMemberDef());
+#endif // FEATURE_REJIT
+}
+
+NativeCodeVersionCollection CodeVersionManager::GetNativeCodeVersions(PTR_MethodDesc pMethod) const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ _ASSERTE(LockOwnedByCurrentThread());
+ return NativeCodeVersionCollection(pMethod, ILCodeVersion());
+}
+
+NativeCodeVersion CodeVersionManager::GetNativeCodeVersion(PTR_MethodDesc pMethod, PCODE codeStartAddress) const
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ _ASSERTE(LockOwnedByCurrentThread());
+
+ NativeCodeVersionCollection nativeCodeVersions = GetNativeCodeVersions(pMethod);
+ for (NativeCodeVersionIterator cur = nativeCodeVersions.Begin(), end = nativeCodeVersions.End(); cur != end; cur++)
+ {
+ if (cur->GetNativeCode() == codeStartAddress)
+ {
+ return *cur;
+ }
+ }
+ return NativeCodeVersion();
+}
+
+#ifndef DACCESS_COMPILE
+HRESULT CodeVersionManager::AddILCodeVersion(Module* pModule, mdMethodDef methodDef, ReJITID rejitId, ILCodeVersion* pILCodeVersion)
+{
+ LIMITED_METHOD_CONTRACT;
+ _ASSERTE(LockOwnedByCurrentThread());
+
+ ILCodeVersioningState* pILCodeVersioningState;
+ HRESULT hr = GetOrCreateILCodeVersioningState(pModule, methodDef, &pILCodeVersioningState);
+ if (FAILED(hr))
+ {
+ _ASSERTE(hr == E_OUTOFMEMORY);
+ return hr;
+ }
+
+ ILCodeVersionNode* pILCodeVersionNode = new (nothrow) ILCodeVersionNode(pModule, methodDef, rejitId);
+ if (pILCodeVersionNode == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+ pILCodeVersioningState->LinkILCodeVersionNode(pILCodeVersionNode);
+ *pILCodeVersion = ILCodeVersion(pILCodeVersionNode);
+ return S_OK;
+}
+
+HRESULT CodeVersionManager::SetActiveILCodeVersions(ILCodeVersion* pActiveVersions, DWORD cActiveVersions, BOOL fEESuspended, CDynArray<CodePublishError> * pErrors)
+{
+ // If the IL version is in the shared domain we need to iterate all domains
+ // looking for instantiations. The domain iterator lock is bigger than
+ // the code version manager lock so we can't do this atomically. In one atomic
+ // update the bookkeeping for IL versioning will happen and then in a second
+ // update the active native code versions will change/code jumpstamps+precodes
+ // will update.
+ //
+ // Note: For all domains other than the shared AppDomain we could do this
+ // atomically, but for now we use the lowest common denominator for all
+ // domains.
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_PREEMPTIVE;
+ CAN_TAKE_LOCK;
+ PRECONDITION(CheckPointer(pActiveVersions));
+ PRECONDITION(CheckPointer(pErrors, NULL_OK));
+ }
+ CONTRACTL_END;
+ _ASSERTE(!LockOwnedByCurrentThread());
+ HRESULT hr = S_OK;
+
+#if DEBUG
+ for (DWORD i = 0; i < cActiveVersions; i++)
+ {
+ ILCodeVersion activeVersion = pActiveVersions[i];
+ if (activeVersion.IsNull())
+ {
+ _ASSERTE(!"The active IL version can't be NULL");
+ }
+ }
+#endif
+
+ // step 1 - mark the IL versions as being active, this ensures that
+ // any new method instantiations added after this point will bind to
+ // the correct version
+ {
+ TableLockHolder(this);
+ for (DWORD i = 0; i < cActiveVersions; i++)
+ {
+ ILCodeVersion activeVersion = pActiveVersions[i];
+ ILCodeVersioningState* pILCodeVersioningState = NULL;
+ if (FAILED(hr = GetOrCreateILCodeVersioningState(activeVersion.GetModule(), activeVersion.GetMethodDef(), &pILCodeVersioningState)))
+ {
+ _ASSERTE(hr == E_OUTOFMEMORY);
+ return hr;
+ }
+ pILCodeVersioningState->SetActiveVersion(activeVersion);
+ }
+ }
+
+ // step 2 - determine the set of pre-existing method instantiations
+
+ // a parallel array to activeVersions
+ // for each ILCodeVersion in activeVersions, this lists the set
+ // MethodDescs that will need to be updated
+ CDynArray<CDynArray<MethodDesc*>> methodDescsToUpdate;
+ CDynArray<CodePublishError> errorRecords;
+ for (DWORD i = 0; i < cActiveVersions; i++)
+ {
+ CDynArray<MethodDesc*>* pMethodDescs = methodDescsToUpdate.Append();
+ if (pMethodDescs == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+ *pMethodDescs = CDynArray<MethodDesc*>();
+
+ MethodDesc* pLoadedMethodDesc = pActiveVersions[i].GetModule()->LookupMethodDef(pActiveVersions[i].GetMethodDef());
+ if (FAILED(hr = CodeVersionManager::EnumerateClosedMethodDescs(pLoadedMethodDesc, pMethodDescs, &errorRecords)))
+ {
+ _ASSERTE(hr == E_OUTOFMEMORY);
+ return hr;
+ }
+ }
+
+ // step 3 - update each pre-existing method instantiation
+ {
+ TableLockHolder lock(this);
+ for (DWORD i = 0; i < cActiveVersions; i++)
+ {
+ // Its possible the active IL version has changed if
+ // another caller made an update while this method wasn't
+ // holding the lock. We will ensure that we synchronize
+ // publishing to whatever version is currently active, even
+ // if that isn't the IL version we set above.
+ //
+ // Note: Although we attempt to handle this case gracefully
+ // it isn't recommended for callers to do this. Racing two calls
+ // that set the IL version to different results means it will be
+ // completely arbitrary which version wins.
+ ILCodeVersion requestedActiveILVersion = pActiveVersions[i];
+ ILCodeVersion activeILVersion = GetActiveILCodeVersion(requestedActiveILVersion.GetModule(), requestedActiveILVersion.GetMethodDef());
+
+ CDynArray<MethodDesc*> methodDescs = methodDescsToUpdate[i];
+ for (int j = 0; j < methodDescs.Count(); j++)
+ {
+ // Get an the active child code version for this method instantiation (it might be NULL, that is OK)
+ NativeCodeVersion activeNativeChild = activeILVersion.GetActiveNativeCodeVersion(methodDescs[j]);
+
+ // Publish that child version, because it is the active native child of the active IL version
+ // Failing to publish is non-fatal, but we do record it so the caller is aware
+ if (FAILED(hr = PublishNativeCodeVersion(methodDescs[j], activeNativeChild, fEESuspended)))
+ {
+ if (FAILED(hr = AddCodePublishError(activeILVersion.GetModule(), activeILVersion.GetMethodDef(), methodDescs[j], hr, &errorRecords)))
+ {
+ _ASSERTE(hr == E_OUTOFMEMORY);
+ return hr;
+ }
+ }
+ }
+ }
+ }
+
+ return S_OK;
+}
+
+HRESULT CodeVersionManager::AddNativeCodeVersion(ILCodeVersion ilCodeVersion, MethodDesc* pClosedMethodDesc, NativeCodeVersion* pNativeCodeVersion)
+{
+ LIMITED_METHOD_CONTRACT;
+ _ASSERTE(LockOwnedByCurrentThread());
+
+ MethodDescVersioningState* pMethodVersioningState;
+ HRESULT hr = GetOrCreateMethodDescVersioningState(pClosedMethodDesc, &pMethodVersioningState);
+ if (FAILED(hr))
+ {
+ _ASSERTE(hr == E_OUTOFMEMORY);
+ return hr;
+ }
+
+ NativeCodeVersionId newId = pMethodVersioningState->AllocateVersionId();
+ NativeCodeVersionNode* pNativeCodeVersionNode = new (nothrow) NativeCodeVersionNode(newId, pClosedMethodDesc, ilCodeVersion.GetVersionId());
+ if (pNativeCodeVersionNode == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ pMethodVersioningState->LinkNativeCodeVersionNode(pNativeCodeVersionNode);
+
+ // the first child added is automatically considered the active one.
+ if (ilCodeVersion.GetActiveNativeCodeVersion(pClosedMethodDesc).IsNull())
+ {
+ pNativeCodeVersionNode->SetActiveChildFlag(TRUE);
+ _ASSERTE(!ilCodeVersion.GetActiveNativeCodeVersion(pClosedMethodDesc).IsNull());
+
+ // the new child shouldn't have any native code. If it did we might need to
+ // publish that code as part of adding the node which would require callers
+ // to pay attention to GC suspension and we'd need to report publishing errors
+ // back to them.
+ _ASSERTE(pNativeCodeVersionNode->GetNativeCode() == NULL);
+ }
+ *pNativeCodeVersion = NativeCodeVersion(pNativeCodeVersionNode);
+ return S_OK;
+}
+
+PCODE CodeVersionManager::PublishVersionableCodeIfNecessary(MethodDesc* pMethodDesc, BOOL fCanBackpatchPrestub)
+{
+ STANDARD_VM_CONTRACT;
+ _ASSERTE(!LockOwnedByCurrentThread());
+ _ASSERTE(pMethodDesc->IsVersionable());
+ _ASSERTE(!pMethodDesc->IsPointingToPrestub() || !pMethodDesc->IsVersionableWithJumpStamp());
+
+ HRESULT hr = S_OK;
+ PCODE pCode = NULL;
+ BOOL fIsJumpStampMethod = pMethodDesc->IsVersionableWithJumpStamp();
+
+ NativeCodeVersion activeVersion;
+ {
+ TableLockHolder lock(this);
+ if (FAILED(hr = GetActiveILCodeVersion(pMethodDesc).GetOrCreateActiveNativeCodeVersion(pMethodDesc, &activeVersion)))
+ {
+ _ASSERTE(hr == E_OUTOFMEMORY);
+ ReportCodePublishError(pMethodDesc->GetModule(), pMethodDesc->GetMemberDef(), pMethodDesc, hr);
+ return NULL;
+ }
+ }
+
+ BOOL fEESuspend = FALSE;
+ while (true)
+ {
+ // compile the code if needed
+ pCode = activeVersion.GetNativeCode();
+ if (pCode == NULL)
+ {
+ pCode = pMethodDesc->PrepareCode(activeVersion);
+ }
+
+ // suspend in preparation for publishing if needed
+ if (fEESuspend)
+ {
+ ThreadSuspend::SuspendEE(ThreadSuspend::SUSPEND_FOR_REJIT);
+ }
+
+ {
+ TableLockHolder lock(this);
+ // The common case is that newActiveCode == activeCode, however we did leave the lock so there is
+ // possibility that the active version has changed. If it has we need to restart the compilation
+ // and publishing process with the new active version instead.
+ //
+ // In theory it should be legitimate to break out of this loop and run the less recent active version,
+ // because ultimately this is a race between one thread that is updating the version and another thread
+ // trying to run the current version. However for back-compat with ReJIT we need to guarantee that
+ // a versioning update at least as late as the profiler JitCompilationFinished callback wins the race.
+ NativeCodeVersion newActiveVersion;
+ if (FAILED(hr = GetActiveILCodeVersion(pMethodDesc).GetOrCreateActiveNativeCodeVersion(pMethodDesc, &newActiveVersion)))
+ {
+ _ASSERTE(hr == E_OUTOFMEMORY);
+ ReportCodePublishError(pMethodDesc->GetModule(), pMethodDesc->GetMemberDef(), pMethodDesc, hr);
+ pCode = NULL;
+ break;
+ }
+ if (newActiveVersion != activeVersion)
+ {
+ activeVersion = newActiveVersion;
+ }
+ else
+ {
+ // if we aren't allowed to backpatch we are done
+ if (!fCanBackpatchPrestub)
+ {
+ break;
+ }
+
+ // attempt to publish the active version still under the lock
+ if (FAILED(hr = PublishNativeCodeVersion(pMethodDesc, activeVersion, fEESuspend)))
+ {
+ // if we need an EESuspend to publish then start over. We have to leave the lock in order to suspend,
+ // and when we leave the lock the active version might change again. However now we know that suspend
+ if (hr == CORPROF_E_RUNTIME_SUSPEND_REQUIRED)
+ {
+ _ASSERTE(!fEESuspend);
+ fEESuspend = true;
+ }
+ else
+ {
+ ReportCodePublishError(pMethodDesc->GetModule(), pMethodDesc->GetMemberDef(), pMethodDesc, hr);
+ pCode = NULL;
+ break;
+ }
+ }
+ else
+ {
+ //success
+ break;
+ }
+ }
+ } // exit lock
+
+ if (fEESuspend)
+ {
+ ThreadSuspend::RestartEE(FALSE, TRUE);
+ }
+ }
+
+ // if the EE is still suspended from breaking in the middle of the loop, resume it
+ if (fEESuspend)
+ {
+ ThreadSuspend::RestartEE(FALSE, TRUE);
+ }
+ return pCode;
+}
+
+HRESULT CodeVersionManager::PublishNativeCodeVersion(MethodDesc* pMethod, NativeCodeVersion nativeCodeVersion, BOOL fEESuspended)
+{
+ LIMITED_METHOD_CONTRACT;
+ _ASSERTE(LockOwnedByCurrentThread());
+ _ASSERTE(pMethod->IsVersionable());
+ HRESULT hr = S_OK;
+ PCODE pCode = nativeCodeVersion.IsNull() ? NULL : nativeCodeVersion.GetNativeCode();
+ if (pMethod->IsVersionableWithPrecode())
+ {
+ Precode* pPrecode = pMethod->GetOrCreatePrecode();
+ if (pCode == NULL)
+ {
+ EX_TRY
+ {
+ pPrecode->Reset();
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+ }
+ else
+ {
+ EX_TRY
+ {
+ hr = pPrecode->SetTargetInterlocked(pCode, FALSE) ? S_OK : E_FAIL;
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+ }
+ }
+ else
+ {
+#ifndef FEATURE_JUMPSTAMP
+ _ASSERTE(!"This platform doesn't support JumpStamp but this method doesn't version with Precode,"
+ " this method can't be updated");
+ return E_FAIL;
+#else
+ MethodDescVersioningState* pVersioningState;
+ if (FAILED(hr = GetOrCreateMethodDescVersioningState(pMethod, &pVersioningState)))
+ {
+ _ASSERTE(hr == E_OUTOFMEMORY);
+ return hr;
+ }
+ return pVersioningState->SyncJumpStamp(nativeCodeVersion, fEESuspended);
+#endif
+ }
+}
+
+// static
+HRESULT CodeVersionManager::EnumerateClosedMethodDescs(
+ MethodDesc* pMD,
+ CDynArray<MethodDesc*> * pClosedMethodDescs,
+ CDynArray<CodePublishError> * pUnsupportedMethodErrors)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_PREEMPTIVE;
+ CAN_TAKE_LOCK;
+ PRECONDITION(CheckPointer(pMD, NULL_OK));
+ PRECONDITION(CheckPointer(pClosedMethodDescs));
+ PRECONDITION(CheckPointer(pUnsupportedMethodErrors));
+ }
+ CONTRACTL_END;
+ HRESULT hr = S_OK;
+ if (pMD == NULL)
+ {
+ // nothing is loaded yet so we're done for this method.
+ return S_OK;
+ }
+
+ if (!pMD->HasClassOrMethodInstantiation())
+ {
+ // We have a JITted non-generic.
+ MethodDesc ** ppMD = pClosedMethodDescs->Append();
+ if (ppMD == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+ *ppMD = pMD;
+ }
+
+ if (!pMD->HasClassOrMethodInstantiation())
+ {
+ // not generic, we're done for this method
+ return S_OK;
+ }
+
+ // Ok, now the case of a generic function (or function on generic class), which
+ // is loaded, and may thus have compiled instantiations.
+ // It's impossible to get to any other kind of domain from the profiling API
+ Module* pModule = pMD->GetModule();
+ mdMethodDef methodDef = pMD->GetMemberDef();
+ BaseDomain * pBaseDomainFromModule = pModule->GetDomain();
+ _ASSERTE(pBaseDomainFromModule->IsAppDomain() ||
+ pBaseDomainFromModule->IsSharedDomain());
+
+ if (pBaseDomainFromModule->IsSharedDomain())
+ {
+ // Iterate through all modules loaded into the shared domain, to
+ // find all instantiations living in the shared domain. This will
+ // include orphaned code (i.e., shared code used by ADs that have
+ // all unloaded), which is good, because orphaned code could get
+ // re-adopted if a new AD is created that can use that shared code
+ hr = EnumerateDomainClosedMethodDescs(
+ NULL, // NULL means to search SharedDomain instead of an AD
+ pModule,
+ methodDef,
+ pClosedMethodDescs,
+ pUnsupportedMethodErrors);
+ }
+ else
+ {
+ // Module is unshared, so just use the module's domain to find instantiations.
+ hr = EnumerateDomainClosedMethodDescs(
+ pBaseDomainFromModule->AsAppDomain(),
+ pModule,
+ methodDef,
+ pClosedMethodDescs,
+ pUnsupportedMethodErrors);
+ }
+ if (FAILED(hr))
+ {
+ _ASSERTE(hr == E_OUTOFMEMORY);
+ return hr;
+ }
+
+ // We want to iterate through all compilations of existing instantiations to
+ // ensure they get marked for rejit. Note: There may be zero instantiations,
+ // but we won't know until we try.
+ if (pBaseDomainFromModule->IsSharedDomain())
+ {
+ // Iterate through all real domains, to find shared instantiations.
+ AppDomainIterator appDomainIterator(TRUE);
+ while (appDomainIterator.Next())
+ {
+ AppDomain * pAppDomain = appDomainIterator.GetDomain();
+ if (pAppDomain->IsUnloading())
+ {
+ continue;
+ }
+ hr = EnumerateDomainClosedMethodDescs(
+ pAppDomain,
+ pModule,
+ methodDef,
+ pClosedMethodDescs,
+ pUnsupportedMethodErrors);
+ if (FAILED(hr))
+ {
+ _ASSERTE(hr == E_OUTOFMEMORY);
+ return hr;
+ }
+ }
+ }
+ return S_OK;
+}
+
+// static
+HRESULT CodeVersionManager::EnumerateDomainClosedMethodDescs(
+ AppDomain * pAppDomainToSearch,
+ Module* pModuleContainingMethodDef,
+ mdMethodDef methodDef,
+ CDynArray<MethodDesc*> * pClosedMethodDescs,
+ CDynArray<CodePublishError> * pUnsupportedMethodErrors)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_PREEMPTIVE;
+ CAN_TAKE_LOCK;
+ PRECONDITION(CheckPointer(pAppDomainToSearch, NULL_OK));
+ PRECONDITION(CheckPointer(pModuleContainingMethodDef));
+ PRECONDITION(CheckPointer(pClosedMethodDescs));
+ PRECONDITION(CheckPointer(pUnsupportedMethodErrors));
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(methodDef != mdTokenNil);
+
+ HRESULT hr;
+
+ BaseDomain * pDomainContainingGenericDefinition = pModuleContainingMethodDef->GetDomain();
+
+#ifdef _DEBUG
+ // If the generic definition is not loaded domain-neutral, then all its
+ // instantiations will also be non-domain-neutral and loaded into the same
+ // domain as the generic definition. So the caller may only pass the
+ // domain containing the generic definition as pAppDomainToSearch
+ if (!pDomainContainingGenericDefinition->IsSharedDomain())
+ {
+ _ASSERTE(pDomainContainingGenericDefinition == pAppDomainToSearch);
+ }
+#endif //_DEBUG
+
+ // If pAppDomainToSearch is NULL, iterate through all existing
+ // instantiations loaded into the SharedDomain. If pAppDomainToSearch is non-NULL,
+ // iterate through all existing instantiations in pAppDomainToSearch, and only consider
+ // instantiations in non-domain-neutral assemblies (as we already covered domain
+ // neutral assemblies when we searched the SharedDomain).
+ LoadedMethodDescIterator::AssemblyIterationMode mode = LoadedMethodDescIterator::kModeSharedDomainAssemblies;
+ // these are the default flags which won't actually be used in shared mode other than
+ // asserting they were specified with their default values
+ AssemblyIterationFlags assemFlags = (AssemblyIterationFlags)(kIncludeLoaded | kIncludeExecution);
+ ModuleIterationOption moduleFlags = (ModuleIterationOption)kModIterIncludeLoaded;
+ if (pAppDomainToSearch != NULL)
+ {
+ mode = LoadedMethodDescIterator::kModeUnsharedADAssemblies;
+ assemFlags = (AssemblyIterationFlags)(kIncludeAvailableToProfilers | kIncludeExecution);
+ moduleFlags = (ModuleIterationOption)kModIterIncludeAvailableToProfilers;
+ }
+ LoadedMethodDescIterator it(
+ pAppDomainToSearch,
+ pModuleContainingMethodDef,
+ methodDef,
+ mode,
+ assemFlags,
+ moduleFlags);
+ CollectibleAssemblyHolder<DomainAssembly *> pDomainAssembly;
+ while (it.Next(pDomainAssembly.This()))
+ {
+ MethodDesc * pLoadedMD = it.Current();
+
+ if (!pLoadedMD->IsVersionable())
+ {
+ // For compatibility with the rejit APIs we ensure certain errors are detected and reported using their
+ // original HRESULTS
+ HRESULT errorHR = GetNonVersionableError(pLoadedMD);
+ if (FAILED(errorHR))
+ {
+ if (FAILED(hr = CodeVersionManager::AddCodePublishError(pModuleContainingMethodDef, methodDef, pLoadedMD, CORPROF_E_FUNCTION_IS_COLLECTIBLE, pUnsupportedMethodErrors)))
+ {
+ _ASSERTE(hr == E_OUTOFMEMORY);
+ return hr;
+ }
+ }
+ continue;
+ }
+
+#ifdef _DEBUG
+ if (!pDomainContainingGenericDefinition->IsSharedDomain())
+ {
+ // Method is defined outside of the shared domain, so its instantiation must
+ // be defined in the AD we're iterating over (pAppDomainToSearch, which, as
+ // asserted above, must be the same domain as the generic's definition)
+ _ASSERTE(pLoadedMD->GetDomain() == pAppDomainToSearch);
+ }
+#endif // _DEBUG
+
+ MethodDesc ** ppMD = pClosedMethodDescs->Append();
+ if (ppMD == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+ *ppMD = pLoadedMD;
+ }
+ return S_OK;
+}
+#endif // DACCESS_COMPILE
+
+
+//---------------------------------------------------------------------------------------
+//
+// Given the default version code for a MethodDesc that is about to published, add
+// a jumpstamp pointing back to the prestub if the currently active version isn't
+// the default one. This called from the PublishMethodHolder.
+//
+// Arguments:
+// * pMD - MethodDesc to jmp-stamp
+// * pCode - Top of the code that was just jitted (using original IL).
+//
+//
+// Return value:
+// * S_OK: Either we successfully did the jmp-stamp, or we didn't have to
+// * Else, HRESULT indicating failure.
+
+// Assumptions:
+// The caller has not yet published pCode to the MethodDesc, so no threads can be
+// executing inside pMD's code yet. Thus, we don't need to suspend the runtime while
+// applying the jump-stamp like we usually do for rejit requests that are made after
+// a function has been JITted.
+//
+#ifndef DACCESS_COMPILE
+HRESULT CodeVersionManager::DoJumpStampIfNecessary(MethodDesc* pMD, PCODE pCode)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ CAN_TAKE_LOCK;
+ PRECONDITION(CheckPointer(pMD));
+ PRECONDITION(pCode != NULL);
+ }
+ CONTRACTL_END;
+
+ HRESULT hr;
+
+ _ASSERTE(LockOwnedByCurrentThread());
+
+ NativeCodeVersion activeCodeVersion = GetActiveILCodeVersion(pMD).GetActiveNativeCodeVersion(pMD);
+ if (activeCodeVersion.IsDefaultVersion())
+ {
+ //Method not requested to be rejitted, nothing to do
+ return S_OK;
+ }
+
+ if (!(pMD->IsVersionable() && pMD->IsVersionableWithJumpStamp()))
+ {
+ return GetNonVersionableError(pMD);
+ }
+
+#ifndef FEATURE_JUMPSTAMP
+ _ASSERTE(!"How did we get here? IsVersionableWithJumpStamp() should have been FALSE above");
+ return S_OK;
+#else
+ MethodDescVersioningState* pVersioningState;
+ if (FAILED(hr = GetOrCreateMethodDescVersioningState(pMD, &pVersioningState)))
+ {
+ _ASSERTE(hr == E_OUTOFMEMORY);
+ return hr;
+ }
+ if (pVersioningState->GetJumpStampState() != MethodDescVersioningState::JumpStampNone)
+ {
+ //JumpStamp already in place
+ return S_OK;
+ }
+ return pVersioningState->JumpStampNativeCode(pCode);
+#endif // FEATURE_JUMPSTAMP
+
+}
+#endif // DACCESS_COMPILE
+
+#ifndef DACCESS_COMPILE
+//static
+void CodeVersionManager::OnAppDomainExit(AppDomain * pAppDomain)
+{
+ LIMITED_METHOD_CONTRACT;
+ // This would clean up all the allocations we have done and synchronize with any threads that might
+ // still be using the data
+ _ASSERTE(!".Net Core shouldn't be doing app domain shutdown - if we start doing so this needs to be implemented");
+}
+#endif
+
+//---------------------------------------------------------------------------------------
+//
+// Small helper to determine whether a given (possibly instantiated generic) MethodDesc
+// is safe to rejit.
+//
+// Arguments:
+// pMD - MethodDesc to test
+// Return Value:
+// S_OK iff pMD is safe to rejit
+// CORPROF_E_FUNCTION_IS_COLLECTIBLE - function can't be rejitted because it is collectible
+//
+
+// static
+#ifndef DACCESS_COMPILE
+HRESULT CodeVersionManager::GetNonVersionableError(MethodDesc* pMD)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ CAN_TAKE_LOCK;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(pMD != NULL);
+
+ // Weird, non-user functions were already weeded out in RequestReJIT(), and will
+ // also never be passed to us by the prestub worker (for the pre-rejit case).
+ _ASSERTE(pMD->IsIL());
+
+ // Any MethodDescs that could be collected are not currently supported. Although we
+ // rule out all Ref.Emit modules in RequestReJIT(), there can still exist types defined
+ // in a non-reflection module and instantiated into a collectible assembly
+ // (e.g., List<MyCollectibleStruct>). In the future we may lift this
+ // restriction by updating the ReJitManager when the collectible assemblies
+ // owning the instantiations get collected.
+ if (pMD->GetLoaderAllocator()->IsCollectible())
+ {
+ return CORPROF_E_FUNCTION_IS_COLLECTIBLE;
+ }
+
+ return S_OK;
+}
+#endif
+
+//---------------------------------------------------------------------------------------
+//
+// Helper that inits a new CodePublishError and adds it to the pErrors array
+//
+// Arguments:
+// * pModule - The module in the module/MethodDef identifier pair for the method which
+// had an error during rejit
+// * methodDef - The MethodDef in the module/MethodDef identifier pair for the method which
+// had an error during rejit
+// * pMD - If available, the specific method instance which had an error during rejit
+// * hrStatus - HRESULT for the rejit error that occurred
+// * pErrors - the list of error records that this method will append to
+//
+// Return Value:
+// * S_OK: error was appended
+// * E_OUTOFMEMORY: Not enough memory to create the new error item. The array is unchanged.
+//
+
+//static
+#ifndef DACCESS_COMPILE
+HRESULT CodeVersionManager::AddCodePublishError(Module* pModule, mdMethodDef methodDef, MethodDesc* pMD, HRESULT hrStatus, CDynArray<CodePublishError> * pErrors)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ if (pErrors == NULL)
+ {
+ return S_OK;
+ }
+
+ CodePublishError* pError = pErrors->Append();
+ if (pError == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+ pError->pModule = pModule;
+ pError->methodDef = methodDef;
+ pError->pMethodDesc = pMD;
+ pError->hrStatus = hrStatus;
+ return S_OK;
+}
+#endif
+
+#ifndef DACCESS_COMPILE
+void CodeVersionManager::ReportCodePublishError(CodePublishError* pErrorRecord)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ CAN_TAKE_LOCK;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ ReportCodePublishError(pErrorRecord->pModule, pErrorRecord->methodDef, pErrorRecord->pMethodDesc, pErrorRecord->hrStatus);
+}
+
+void CodeVersionManager::ReportCodePublishError(Module* pModule, mdMethodDef methodDef, MethodDesc* pMD, HRESULT hrStatus)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ CAN_TAKE_LOCK;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+#ifdef FEATURE_REJIT
+ BOOL isRejitted = FALSE;
+ {
+ TableLockHolder(this);
+ isRejitted = !GetActiveILCodeVersion(pModule, methodDef).IsDefaultVersion();
+ }
+
+ // this isn't perfect, we might be activating a tiered jitting variation of a rejitted
+ // method for example. If it proves to be an issue we can revisit.
+ if (isRejitted)
+ {
+ ReJitManager::ReportReJITError(pModule, methodDef, pMD, hrStatus);
+ }
+#endif
+}
+#endif // DACCESS_COMPILE
+
+//---------------------------------------------------------------------------------------
+//
+// PrepareCodeConfig::SetNativeCode() calls this to determine if there's a non-default code
+// version requested for a MethodDesc that has just been jitted for the first time.
+// This is also called when methods are being restored in NGEN images. The sequence looks like:
+// *Enter holder
+// Enter code version manager lock
+// DoJumpStampIfNecessary
+// *Runtime code publishes/restores method
+// *Exit holder
+// Leave code version manager lock
+// Send rejit error callbacks if needed
+//
+//
+// #PublishCode:
+// Note that the runtime needs to publish/restore the PCODE while this holder is
+// on the stack, so it can happen under the code version manager's lock.
+// This prevents a race with a profiler that calls
+// RequestReJIT just as the method finishes compiling. In particular, the locking ensures
+// atomicity between this set of steps (performed in DoJumpStampIfNecessary):
+// * (1) Checking whether there is a non-default version for this MD
+// * (2) If not, skip doing the jmp-stamp
+// * (3) Publishing the PCODE
+//
+// with respect to these steps performed in RequestReJIT:
+// * (a) Is PCODE published yet?
+// * (b) Create non-default ILCodeVersion which the prestub will
+// consult when it JITs the original IL
+//
+// Without this atomicity, we could get the ordering (1), (2), (a), (b), (3), resulting
+// in the rejit request getting completely ignored (i.e., we file away the new ILCodeVersion
+// AFTER the prestub checks for it).
+//
+// A similar race is possible for code being restored. In that case the restoring thread
+// does:
+// * (1) Check if there is a non-default ILCodeVersion for this MD
+// * (2) If not, no need to jmp-stamp
+// * (3) Restore the MD
+
+// And RequestRejit does:
+// * (a) [In LoadedMethodDescIterator] Is a potential MD restored yet?
+// * (b) [In EnumerateDomainClosedMethodDescs] If not, don't queue it for jump-stamping
+//
+// Same ordering (1), (2), (a), (b), (3) results in missing both opportunities to jump
+// stamp.
+
+#if !defined(DACCESS_COMPILE) && !defined(CROSSGEN_COMPILE)
+PublishMethodHolder::PublishMethodHolder(MethodDesc* pMethodDesc, PCODE pCode) :
+ m_pMD(NULL), m_hr(S_OK)
+{
+ // This method can't have a contract because entering the table lock
+ // below increments GCNoTrigger count. Contracts always revert these changes
+ // at the end of the method but we need the incremented count to flow out of the
+ // method. The balancing decrement occurs in the destructor.
+ STATIC_CONTRACT_NOTHROW;
+ STATIC_CONTRACT_GC_NOTRIGGER;
+ STATIC_CONTRACT_CAN_TAKE_LOCK;
+ STATIC_CONTRACT_MODE_ANY;
+
+ // We come here from the PreStub and from MethodDesc::CheckRestore
+ // The method should be effectively restored, but we haven't yet
+ // cleared the unrestored bit so we can't assert pMethodDesc->IsRestored()
+ // We can assert:
+ _ASSERTE(pMethodDesc->GetMethodTable()->IsRestored());
+
+ if (pCode != NULL)
+ {
+ m_pMD = pMethodDesc;
+ CodeVersionManager* pCodeVersionManager = pMethodDesc->GetCodeVersionManager();
+ pCodeVersionManager->EnterLock();
+ m_hr = pCodeVersionManager->DoJumpStampIfNecessary(pMethodDesc, pCode);
+ }
+}
+
+
+PublishMethodHolder::~PublishMethodHolder()
+{
+ // This method can't have a contract because leaving the table lock
+ // below decrements GCNoTrigger count. Contracts always revert these changes
+ // at the end of the method but we need the decremented count to flow out of the
+ // method. The balancing increment occurred in the constructor.
+ STATIC_CONTRACT_NOTHROW;
+ STATIC_CONTRACT_GC_TRIGGERS; // NOTRIGGER until we leave the lock
+ STATIC_CONTRACT_CAN_TAKE_LOCK;
+ STATIC_CONTRACT_MODE_ANY;
+
+ if (m_pMD)
+ {
+ CodeVersionManager* pCodeVersionManager = m_pMD->GetCodeVersionManager();
+ pCodeVersionManager->LeaveLock();
+ if (FAILED(m_hr))
+ {
+ pCodeVersionManager->ReportCodePublishError(m_pMD->GetModule(), m_pMD->GetMemberDef(), m_pMD, m_hr);
+ }
+ }
+}
+
+PublishMethodTableHolder::PublishMethodTableHolder(MethodTable* pMethodTable) :
+ m_pMethodTable(NULL)
+{
+ // This method can't have a contract because entering the table lock
+ // below increments GCNoTrigger count. Contracts always revert these changes
+ // at the end of the method but we need the incremented count to flow out of the
+ // method. The balancing decrement occurs in the destructor.
+ STATIC_CONTRACT_NOTHROW;
+ STATIC_CONTRACT_GC_NOTRIGGER;
+ STATIC_CONTRACT_CAN_TAKE_LOCK;
+ STATIC_CONTRACT_MODE_ANY;
+
+ // We come here from MethodTable::SetIsRestored
+ // The method table should be effectively restored, but we haven't yet
+ // cleared the unrestored bit so we can't assert pMethodTable->IsRestored()
+
+ m_pMethodTable = pMethodTable;
+ CodeVersionManager* pCodeVersionManager = pMethodTable->GetModule()->GetCodeVersionManager();
+ pCodeVersionManager->EnterLock();
+ MethodTable::IntroducedMethodIterator itMethods(pMethodTable, FALSE);
+ for (; itMethods.IsValid(); itMethods.Next())
+ {
+ // Although the MethodTable is restored, the methods might not be.
+ // We need to be careful to only query portions of the MethodDesc
+ // that work in a partially restored state. The only methods that need
+ // further restoration are IL stubs (which aren't rejittable) and
+ // generic methods. The only generic methods directly accesible from
+ // the MethodTable are definitions. GetNativeCode() on generic defs
+ // will run succesfully and return NULL which short circuits the
+ // rest of the logic.
+ MethodDesc * pMD = itMethods.GetMethodDesc();
+ PCODE pCode = pMD->GetNativeCode();
+ if (pCode != NULL)
+ {
+ HRESULT hr = pCodeVersionManager->DoJumpStampIfNecessary(pMD, pCode);
+ if (FAILED(hr))
+ {
+ CodeVersionManager::AddCodePublishError(pMD->GetModule(), pMD->GetMemberDef(), pMD, hr, &m_errors);
+ }
+ }
+ }
+}
+
+
+PublishMethodTableHolder::~PublishMethodTableHolder()
+{
+ // This method can't have a contract because leaving the table lock
+ // below decrements GCNoTrigger count. Contracts always revert these changes
+ // at the end of the method but we need the decremented count to flow out of the
+ // method. The balancing increment occurred in the constructor.
+ STATIC_CONTRACT_NOTHROW;
+ STATIC_CONTRACT_GC_TRIGGERS; // NOTRIGGER until we leave the lock
+ STATIC_CONTRACT_CAN_TAKE_LOCK;
+ STATIC_CONTRACT_MODE_ANY;
+
+ if (m_pMethodTable)
+ {
+ CodeVersionManager* pCodeVersionManager = m_pMethodTable->GetModule()->GetCodeVersionManager();
+ pCodeVersionManager->LeaveLock();
+ for (int i = 0; i < m_errors.Count(); i++)
+ {
+ pCodeVersionManager->ReportCodePublishError(&(m_errors[i]));
+ }
+ }
+}
+#endif // !defined(DACCESS_COMPILE) && !defined(CROSSGEN_COMPILE)
+
+#endif // FEATURE_CODE_VERSIONING
+