Files
Elinor Fung c37e13fc1a Remove GetDomain functions on assorted method/type/assembly classes which are always in the single app domain (#98135)
- Remove `GetDomain` functions on `MethodDesc`, `MethodTable`, `TypeDesc`, and `TypeHandle`. They always return the single current app domain.
- Remove `GetDomain` functions and variables on  `Assembly`, `DomainAssembly`, and `Module`. The actual domain they are constructed with is always the single current app domain.
2024-02-08 13:58:15 -08:00

4105 lines
125 KiB
C++

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// ===========================================================================
// File: Method.CPP
//
//
// See the book of the runtime entry for overall design:
// file:../../doc/BookOfTheRuntime/ClassLoader/MethodDescDesign.doc
//
#include "common.h"
#include "excep.h"
#include "dbginterface.h"
#include "ecall.h"
#include "eeconfig.h"
#include "mlinfo.h"
#include "dllimport.h"
#include "generics.h"
#include "genericdict.h"
#include "typedesc.h"
#include "typestring.h"
#include "virtualcallstub.h"
#include "jitinterface.h"
#include "runtimehandles.h"
#include "eventtrace.h"
#include "interoputil.h"
#include "prettyprintsig.h"
#include "formattype.h"
#include "fieldmarshaler.h"
#include "versionresilienthashcode.h"
#include "typehashingalgorithms.h"
#ifdef FEATURE_COMINTEROP
#include "comcallablewrapper.h"
#include "clrtocomcall.h"
#endif
#ifdef FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
GVAL_IMPL(DWORD, g_MiniMetaDataBuffMaxSize);
GVAL_IMPL(TADDR, g_MiniMetaDataBuffAddress);
#endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
// forward decl
bool FixupSignatureContainingInternalTypes(
DataImage * image,
PCCOR_SIGNATURE pSig,
DWORD cSig,
bool checkOnly = false);
// Alias ComPlusCallMethodDesc to regular MethodDesc to simplify definition of the size table
#ifndef FEATURE_COMINTEROP
#define ComPlusCallMethodDesc MethodDesc
#endif
// Verify that the structure sizes of our MethodDescs support proper
// aligning for atomic stub replacement.
//
static_assert_no_msg((sizeof(MethodDescChunk) & MethodDesc::ALIGNMENT_MASK) == 0);
static_assert_no_msg((sizeof(MethodDesc) & MethodDesc::ALIGNMENT_MASK) == 0);
static_assert_no_msg((sizeof(FCallMethodDesc) & MethodDesc::ALIGNMENT_MASK) == 0);
static_assert_no_msg((sizeof(NDirectMethodDesc) & MethodDesc::ALIGNMENT_MASK) == 0);
static_assert_no_msg((sizeof(EEImplMethodDesc) & MethodDesc::ALIGNMENT_MASK) == 0);
static_assert_no_msg((sizeof(ArrayMethodDesc) & MethodDesc::ALIGNMENT_MASK) == 0);
static_assert_no_msg((sizeof(ComPlusCallMethodDesc) & MethodDesc::ALIGNMENT_MASK) == 0);
static_assert_no_msg((sizeof(DynamicMethodDesc) & MethodDesc::ALIGNMENT_MASK) == 0);
#define METHOD_DESC_SIZES(adjustment) \
adjustment + sizeof(MethodDesc), /* mcIL */ \
adjustment + sizeof(FCallMethodDesc), /* mcFCall */ \
adjustment + sizeof(NDirectMethodDesc), /* mcNDirect */ \
adjustment + sizeof(EEImplMethodDesc), /* mcEEImpl */ \
adjustment + sizeof(ArrayMethodDesc), /* mcArray */ \
adjustment + sizeof(InstantiatedMethodDesc), /* mcInstantiated */ \
adjustment + sizeof(ComPlusCallMethodDesc), /* mcComInterOp */ \
adjustment + sizeof(DynamicMethodDesc) /* mcDynamic */
const BYTE MethodDesc::s_ClassificationSizeTable[] = {
// This is the raw
METHOD_DESC_SIZES(0),
// This extended part of the table is used for faster MethodDesc size lookup.
// We index using optional slot flags into it
METHOD_DESC_SIZES(sizeof(NonVtableSlot)),
METHOD_DESC_SIZES(sizeof(MethodImpl)),
METHOD_DESC_SIZES(sizeof(NonVtableSlot) + sizeof(MethodImpl)),
METHOD_DESC_SIZES(sizeof(NativeCodeSlot)),
METHOD_DESC_SIZES(sizeof(NonVtableSlot) + sizeof(NativeCodeSlot)),
METHOD_DESC_SIZES(sizeof(MethodImpl) + sizeof(NativeCodeSlot)),
METHOD_DESC_SIZES(sizeof(NonVtableSlot) + sizeof(MethodImpl) + sizeof(NativeCodeSlot)),
#ifdef FEATURE_COMINTEROP
METHOD_DESC_SIZES(sizeof(ComPlusCallInfo)),
METHOD_DESC_SIZES(sizeof(NonVtableSlot) + sizeof(ComPlusCallInfo)),
METHOD_DESC_SIZES(sizeof(MethodImpl) + sizeof(ComPlusCallInfo)),
METHOD_DESC_SIZES(sizeof(NonVtableSlot) + sizeof(MethodImpl) + sizeof(ComPlusCallInfo)),
METHOD_DESC_SIZES(sizeof(NativeCodeSlot) + sizeof(ComPlusCallInfo)),
METHOD_DESC_SIZES(sizeof(NonVtableSlot) + sizeof(NativeCodeSlot) + sizeof(ComPlusCallInfo)),
METHOD_DESC_SIZES(sizeof(MethodImpl) + sizeof(NativeCodeSlot) + sizeof(ComPlusCallInfo)),
METHOD_DESC_SIZES(sizeof(NonVtableSlot) + sizeof(MethodImpl) + sizeof(NativeCodeSlot) + sizeof(ComPlusCallInfo))
#endif
};
#ifndef FEATURE_COMINTEROP
#undef ComPlusCallMethodDesc
#endif
class ArgIteratorBaseForPInvoke : public ArgIteratorBase
{
protected:
FORCEINLINE BOOL IsRegPassedStruct(MethodTable* pMT)
{
return pMT->GetNativeLayoutInfo()->IsNativeStructPassedInRegisters();
}
};
class PInvokeArgIterator : public ArgIteratorTemplate<ArgIteratorBaseForPInvoke>
{
public:
PInvokeArgIterator(MetaSig* pSig)
{
m_pSig = pSig;
}
};
//*******************************************************************************
SIZE_T MethodDesc::SizeOf()
{
LIMITED_METHOD_DAC_CONTRACT;
SIZE_T size = s_ClassificationSizeTable[m_wFlags &
(mdcClassification
| mdcHasNonVtableSlot
| mdcMethodImpl
| mdcHasNativeCodeSlot)];
return size;
}
/*********************************************************************/
#ifndef DACCESS_COMPILE
BOOL NDirectMethodDesc::HasDefaultDllImportSearchPathsAttribute()
{
CONTRACTL
{
THROWS;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;
if(IsDefaultDllImportSearchPathsAttributeCached())
{
return (ndirect.m_wFlags & kDefaultDllImportSearchPathsStatus) != 0;
}
BOOL attributeIsFound = GetDefaultDllImportSearchPathsAttributeValue(GetModule(),GetMemberDef(),&ndirect.m_DefaultDllImportSearchPathsAttributeValue);
if(attributeIsFound )
{
InterlockedSetNDirectFlags(kDefaultDllImportSearchPathsIsCached | kDefaultDllImportSearchPathsStatus);
}
else
{
InterlockedSetNDirectFlags(kDefaultDllImportSearchPathsIsCached);
}
return (ndirect.m_wFlags & kDefaultDllImportSearchPathsStatus) != 0;
}
#endif //!DACCESS_COMPILE
//*******************************************************************************
#ifndef DACCESS_COMPILE
VOID MethodDesc::EnsureActive()
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
}
CONTRACTL_END;
GetMethodTable()->EnsureInstanceActive();
if (HasMethodInstantiation() && !IsGenericMethodDefinition())
{
Instantiation methodInst = GetMethodInstantiation();
for (DWORD i = 0; i < methodInst.GetNumArgs(); ++i)
{
MethodTable * pMT = methodInst[i].GetMethodTable();
if (pMT)
pMT->EnsureInstanceActive();
}
}
}
#endif //!DACCESS_COMPILE
//*******************************************************************************
CHECK MethodDesc::CheckActivated()
{
WRAPPER_NO_CONTRACT;
CHECK(GetModule()->CheckActivated());
CHECK_OK;
}
#ifndef DACCESS_COMPILE
//*******************************************************************************
LoaderAllocator * MethodDesc::GetDomainSpecificLoaderAllocator()
{
if (GetLoaderModule()->IsCollectible())
{
return GetLoaderAllocator();
}
else
{
return ::GetAppDomain()->GetLoaderAllocator();
}
}
#endif //!DACCESS_COMPILE
//*******************************************************************************
LPCUTF8 MethodDesc::GetNameThrowing()
{
CONTRACTL
{
THROWS;
}
CONTRACTL_END;
LPCUTF8 result = GetName();
if (result == NULL)
{
ThrowHR(COR_E_BADIMAGEFORMAT, BFA_METADATA_CORRUPT);
}
return result;
}
//*******************************************************************************
LPCUTF8 MethodDesc::GetName(USHORT slot)
{
// MethodDesc::GetDeclMethodDesc can throw.
WRAPPER_NO_CONTRACT;
MethodDesc *pDeclMD = GetDeclMethodDesc((UINT32)slot);
CONSISTENCY_CHECK(IsInterface() || !pDeclMD->IsInterface());
return pDeclMD->GetName();
}
//*******************************************************************************
LPCUTF8 MethodDesc::GetName()
{
CONTRACTL
{
if (FORBIDGC_LOADER_USE_ENABLED()) NOTHROW; else THROWS; // MethodImpl::FindMethodDesc can throw.
GC_NOTRIGGER;
FORBID_FAULT;
SUPPORTS_DAC;
}
CONTRACTL_END;
if (IsArray())
{
// Array classes don't have metadata tokens
return dac_cast<PTR_ArrayMethodDesc>(this)->GetMethodName();
}
else if (IsNoMetadata())
{
// LCG methods don't have metadata tokens
return dac_cast<PTR_DynamicMethodDesc>(this)->GetMethodName();
}
else
{
// Get the metadata string name for this method
LPCUTF8 result = NULL;
if (FAILED(GetMDImport()->GetNameOfMethodDef(GetMemberDef(), &result)))
{
result = NULL;
}
return(result);
}
}
#ifndef DACCESS_COMPILE
/*
* Function to get a method's name, its namespace
*/
VOID MethodDesc::GetMethodInfoNoSig(SString &namespaceOrClassName, SString &methodName)
{
static LPCWSTR pDynamicClassName = W("dynamicClass");
// namespace
if(IsDynamicMethod())
namespaceOrClassName.Append(pDynamicClassName);
else
TypeString::AppendType(namespaceOrClassName, TypeHandle(GetMethodTable()));
// name
methodName.AppendUTF8(GetName());
}
/*
* Function to get a method's name, its namespace and signature (legacy format)
*/
VOID MethodDesc::GetMethodInfo(SString &namespaceOrClassName, SString &methodName, SString &methodSignature)
{
GetMethodInfoNoSig(namespaceOrClassName, methodName);
// signature
CQuickBytes qbOut;
ULONG cSig = 0;
PCCOR_SIGNATURE pSig;
GetSig(&pSig, &cSig);
PrettyPrintSigInternalLegacy(pSig, cSig, " ", &qbOut, GetMDImport());
methodSignature.AppendUTF8((char *)qbOut.Ptr());
}
/*
* Function to get a method's name, its namespace and signature (new format)
*/
VOID MethodDesc::GetMethodInfoWithNewSig(SString &namespaceOrClassName, SString &methodName, SString &methodSignature)
{
GetMethodInfoNoSig(namespaceOrClassName, methodName);
// signature
CQuickBytes qbOut;
ULONG cSig = 0;
PCCOR_SIGNATURE pSig;
GetSig(&pSig, &cSig);
PrettyPrintSig(pSig, (DWORD)cSig, "", &qbOut, GetMDImport(), NULL);
methodSignature.AppendUTF8((char *)qbOut.Ptr());
}
/*
* Function to get a method's full name, something like
* void [System.Private.CoreLib]System.StubHelpers.BSTRMarshaler::ClearNative(native int)
*/
VOID MethodDesc::GetFullMethodInfo(SString& fullMethodSigName)
{
SString namespaceOrClassName, methodName;
GetMethodInfoNoSig(namespaceOrClassName, methodName);
// signature
CQuickBytes qbOut;
ULONG cSig = 0;
PCCOR_SIGNATURE pSig;
SString methodFullName;
methodFullName.AppendPrintf(
(LPCUTF8)"[%s] %s::%s",
GetModule()->GetAssembly()->GetSimpleName(),
namespaceOrClassName.GetUTF8(),
methodName.GetUTF8());
GetSig(&pSig, &cSig);
PrettyPrintSig(pSig, (DWORD)cSig, methodFullName.GetUTF8(), &qbOut, GetMDImport(), NULL);
fullMethodSigName.AppendUTF8((char *)qbOut.Ptr());
}
#endif
//*******************************************************************************
void MethodDesc::GetSig(PCCOR_SIGNATURE *ppSig, DWORD *pcSig)
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
FORBID_FAULT;
SUPPORTS_DAC;
}
CONTRACTL_END
if (HasStoredSig())
{
PTR_StoredSigMethodDesc pSMD = dac_cast<PTR_StoredSigMethodDesc>(this);
if (pSMD->HasStoredMethodSig() || GetClassification()==mcDynamic)
{
*ppSig = pSMD->GetStoredMethodSig(pcSig);
PREFIX_ASSUME(*ppSig != NULL);
return;
}
}
GetSigFromMetadata(GetMDImport(), ppSig, pcSig);
PREFIX_ASSUME(*ppSig != NULL);
}
//*******************************************************************************
// get a function signature from its metadata
// Arguments:
// input:
// importer the metatdata importer to be used
// output:
// ppSig the function signature
// pcSig number of elements in the signature
void MethodDesc::GetSigFromMetadata(IMDInternalImport * importer,
PCCOR_SIGNATURE * ppSig,
DWORD * pcSig)
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
FORBID_FAULT;
SUPPORTS_DAC;
}
CONTRACTL_END
if (FAILED(importer->GetSigOfMethodDef(GetMemberDef(), pcSig, ppSig)))
{ // Class loader already asked for signature, so this should always succeed (unless there's a
// bug or a new code path)
_ASSERTE(!"If this ever fires, then this method should return HRESULT");
*ppSig = NULL;
*pcSig = 0;
}
}
//*******************************************************************************
PCCOR_SIGNATURE MethodDesc::GetSig()
{
WRAPPER_NO_CONTRACT;
PCCOR_SIGNATURE pSig;
DWORD cSig;
GetSig(&pSig, &cSig);
PREFIX_ASSUME(pSig != NULL);
return pSig;
}
Signature MethodDesc::GetSignature()
{
WRAPPER_NO_CONTRACT;
SUPPORTS_DAC;
PCCOR_SIGNATURE pSig;
DWORD cSig;
GetSig(&pSig, &cSig);
PREFIX_ASSUME(pSig != NULL);
return Signature(pSig, cSig);
}
PCODE MethodDesc::GetMethodEntryPoint()
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
SUPPORTS_DAC;
}
CONTRACTL_END;
// Similarly to SetMethodEntryPoint(), it is up to the caller to ensure that calls to this function are appropriately
// synchronized
// Keep implementations of MethodDesc::GetMethodEntryPoint and MethodDesc::GetAddrOfSlot in sync!
if (HasNonVtableSlot())
{
SIZE_T size = GetBaseSize();
TADDR pSlot = dac_cast<TADDR>(this) + size;
return *PTR_PCODE(pSlot);
}
_ASSERTE(GetMethodTable()->IsCanonicalMethodTable());
return GetMethodTable()->GetSlot(GetSlot());
}
PTR_PCODE MethodDesc::GetAddrOfSlot()
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
SUPPORTS_DAC;
}
CONTRACTL_END;
// Keep implementations of MethodDesc::GetMethodEntryPoint and MethodDesc::GetAddrOfSlot in sync!
if (HasNonVtableSlot())
{
SIZE_T size = GetBaseSize();
return PTR_PCODE(dac_cast<TADDR>(this) + size);
}
_ASSERTE(GetMethodTable()->IsCanonicalMethodTable());
return GetMethodTable()->GetSlotPtr(GetSlot());
}
//*******************************************************************************
PTR_MethodDesc MethodDesc::GetDeclMethodDesc(UINT32 slotNumber)
{
CONTRACTL {
WRAPPER(THROWS);
WRAPPER(GC_TRIGGERS);
INSTANCE_CHECK;
} CONTRACTL_END;
MethodDesc *pMDResult = this;
// If the MethodDesc is not itself a methodImpl, but it is not in its native
// slot, then someone (perhaps itself) must have overridden a methodImpl
// in a parent, which causes the method to get put into all of the methodImpl
// slots. So, the MethodDesc is implicitly a methodImpl without containing
// the data. To find the real methodImpl MethodDesc, climb the inheritance
// hierarchy checking the native slot on the way.
if ((UINT32)pMDResult->GetSlot() != slotNumber)
{
while (!pMDResult->IsMethodImpl())
{
CONSISTENCY_CHECK(CheckPointer(pMDResult->GetMethodTable()->GetParentMethodTable()));
CONSISTENCY_CHECK(slotNumber < pMDResult->GetMethodTable()->GetParentMethodTable()->GetNumVirtuals());
pMDResult = pMDResult->GetMethodTable()->GetParentMethodTable()->GetMethodDescForSlot(slotNumber);
}
{
CONSISTENCY_CHECK(pMDResult->IsMethodImpl());
MethodImpl *pImpl = pMDResult->GetMethodImpl();
pMDResult = pImpl->FindMethodDesc(slotNumber, PTR_MethodDesc(pMDResult));
}
// It is possible that a methodImpl'd slot got copied into another slot because
// of slot unification, for example:
// C1::A is methodImpled with C2::B
// C1::B is methodImpled with C2::C
// this means that through slot unification that A is tied to B and B is tied to C,
// so A is tied to C even though C does not have a methodImpl entry specifically
// relating to that slot. In this case, we recurse to the parent type and ask the
// same question again.
if (pMDResult->GetSlot() != slotNumber)
{
MethodTable * pMTOfMD = pMDResult->GetMethodTable();
CONSISTENCY_CHECK(slotNumber < pMTOfMD->GetParentMethodTable()->GetNumVirtuals());
pMDResult = pMTOfMD->GetParentMethodTable()->GetMethodDescForSlot(slotNumber);
pMDResult = pMDResult->GetDeclMethodDesc(slotNumber);
}
}
CONSISTENCY_CHECK(CheckPointer(pMDResult));
CONSISTENCY_CHECK((UINT32)pMDResult->GetSlot() == slotNumber);
return PTR_MethodDesc(pMDResult);
}
//*******************************************************************************
// Returns a hash for the method.
// The hash will be the same for the method across multiple process runs.
#ifndef DACCESS_COMPILE
COUNT_T MethodDesc::GetStableHash()
{
WRAPPER_NO_CONTRACT;
const char * className = NULL;
if (IsLCGMethod())
{
className = "DynamicClass";
}
else if (IsILStub())
{
className = ILStubResolver::GetStubClassName(this);
}
if (className == NULL)
{
return GetVersionResilientMethodHashCode(this);
}
else
{
int typeHash = ComputeNameHashCode("", className);
return typeHash ^ ComputeNameHashCode(GetName());
}
}
#endif // DACCESS_COMPILE
//*******************************************************************************
// Get the number of type parameters to a generic method
DWORD MethodDesc::GetNumGenericMethodArgs()
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
FORBID_FAULT;
CANNOT_TAKE_LOCK;
SUPPORTS_DAC;
}
CONTRACTL_END
if (GetClassification() == mcInstantiated)
{
InstantiatedMethodDesc *pIMD = AsInstantiatedMethodDesc();
return pIMD->m_wNumGenericArgs;
}
else return 0;
}
//*******************************************************************************
MethodTable * MethodDesc::GetExactDeclaringType(MethodTable * ownerOrSubType)
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;
MethodTable * pMT = GetMethodTable();
// Fast path for typical case.
if (ownerOrSubType == pMT)
return pMT;
// If we come here for array method, the typedef tokens inside GetMethodTableMatchingParentClass
// will match, but the types are actually from unrelated arrays, so the result would be incorrect.
_ASSERTE(!IsArray());
return ownerOrSubType->GetMethodTableMatchingParentClass(pMT);
}
//*******************************************************************************
Instantiation MethodDesc::GetExactClassInstantiation(TypeHandle possibleObjType)
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
FORBID_FAULT;
SUPPORTS_DAC;
}
CONTRACTL_END
return (possibleObjType.IsNull()
? GetClassInstantiation()
: possibleObjType.GetInstantiationOfParentClass(GetMethodTable()));
}
//*******************************************************************************
BOOL MethodDesc::HasSameMethodDefAs(MethodDesc * pMD)
{
LIMITED_METHOD_CONTRACT;
if (this == pMD)
return TRUE;
return (GetMemberDef() == pMD->GetMemberDef()) && (GetModule() == pMD->GetModule());
}
//*******************************************************************************
BOOL MethodDesc::IsTypicalSharedInstantiation()
{
WRAPPER_NO_CONTRACT;
Instantiation classInst = GetMethodTable()->GetInstantiation();
if (!ClassLoader::IsTypicalSharedInstantiation(classInst))
return FALSE;
if (IsGenericMethodDefinition())
return FALSE;
Instantiation methodInst = GetMethodInstantiation();
if (!ClassLoader::IsTypicalSharedInstantiation(methodInst))
return FALSE;
return TRUE;
}
//*******************************************************************************
Instantiation MethodDesc::LoadMethodInstantiation()
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
INJECT_FAULT(COMPlusThrowOM(););
}
CONTRACTL_END
if (IsGenericMethodDefinition() && !IsTypicalMethodDefinition())
{
return LoadTypicalMethodDefinition()->GetMethodInstantiation();
}
else
return GetMethodInstantiation();
}
//*******************************************************************************
Module *MethodDesc::GetDefiningModuleForOpenMethod()
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
FORBID_FAULT;
}
CONTRACTL_END
Module *pModule = GetMethodTable()->GetDefiningModuleForOpenType();
if (pModule != NULL)
return pModule;
if (IsGenericMethodDefinition())
return GetModule();
Instantiation inst = GetMethodInstantiation();
for (DWORD i = 0; i < inst.GetNumArgs(); i++)
{
pModule = inst[i].GetDefiningModuleForOpenType();
if (pModule != NULL)
return pModule;
}
return NULL;
}
//*******************************************************************************
BOOL MethodDesc::ContainsGenericVariables()
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
FORBID_FAULT;
}
CONTRACTL_END
// If this is a method of a generic type, does the type have
// non-instantiated type arguments
if (TypeHandle(GetMethodTable()).ContainsGenericVariables())
return TRUE;
if (IsGenericMethodDefinition())
return TRUE;
// If this is an instantiated generic method, are there are any generic type variables
if (GetNumGenericMethodArgs() != 0)
{
Instantiation methodInst = GetMethodInstantiation();
for (DWORD i = 0; i < methodInst.GetNumArgs(); i++)
{
if (methodInst[i].ContainsGenericVariables())
return TRUE;
}
}
return FALSE;
}
//*******************************************************************************
BOOL MethodDesc::IsTightlyBoundToMethodTable()
{
WRAPPER_NO_CONTRACT;
SUPPORTS_DAC;
// Anything with the real vtable slot is tightly bound
if (!HasNonVtableSlot())
return TRUE;
// All instantiations of generic methods are stored in the InstMethHashTable.
if (HasMethodInstantiation())
{
if (IsGenericMethodDefinition())
return TRUE;
else
return FALSE;
}
// Wrapper stubs are stored in the InstMethHashTable, e.g. for static methods in generic classes
if (IsWrapperStub())
return FALSE;
return TRUE;
}
#ifndef DACCESS_COMPILE
//*******************************************************************************
// Update flags in a thread safe manner.
WORD MethodDesc::InterlockedUpdateFlags(WORD wMask, BOOL fSet)
{
CONTRACTL
{
THROWS;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;
WORD wOldState = m_wFlags;
DWORD dwMask = wMask;
// We need to make this operation atomic (multiple threads can play with the flags field at the same time). But the flags field
// is a word and we only have interlock operations over dwords. So we round down the flags field address to the nearest aligned
// dword (along with the intended bitfield mask). Note that we make the assumption that the flags word is aligned itself, so we
// only have two possibilities: the field already lies on a dword boundary or it's precisely one word out.
LONG* pdwFlags = (LONG*)((ULONG_PTR)&m_wFlags - (offsetof(MethodDesc, m_wFlags) & 0x3));
#ifdef _PREFAST_
#pragma warning(push)
#pragma warning(disable:6326) // "Suppress PREFast warning about comparing two constants"
#endif // _PREFAST_
#if BIGENDIAN
if ((offsetof(MethodDesc, m_wFlags) & 0x3) == 0) {
#else // !BIGENDIAN
if ((offsetof(MethodDesc, m_wFlags) & 0x3) != 0) {
#endif // !BIGENDIAN
static_assert_no_msg(sizeof(m_wFlags) == 2);
dwMask <<= 16;
}
#ifdef _PREFAST_
#pragma warning(pop)
#endif
if (fSet)
InterlockedOr(pdwFlags, dwMask);
else
InterlockedAnd(pdwFlags, ~dwMask);
return wOldState;
}
WORD MethodDesc::InterlockedUpdateFlags3(WORD wMask, BOOL fSet)
{
LIMITED_METHOD_CONTRACT;
WORD wOldState = m_wFlags3AndTokenRemainder;
DWORD dwMask = wMask;
// We need to make this operation atomic (multiple threads can play with the flags field at the same time). But the flags field
// is a word and we only have interlock operations over dwords. So we round down the flags field address to the nearest aligned
// dword (along with the intended bitfield mask). Note that we make the assumption that the flags word is aligned itself, so we
// only have two possibilities: the field already lies on a dword boundary or it's precisely one word out.
LONG* pdwFlags = (LONG*)((ULONG_PTR)&m_wFlags3AndTokenRemainder - (offsetof(MethodDesc, m_wFlags3AndTokenRemainder) & 0x3));
#ifdef _PREFAST_
#pragma warning(push)
#pragma warning(disable:6326) // "Suppress PREFast warning about comparing two constants"
#endif // _PREFAST_
#if BIGENDIAN
if ((offsetof(MethodDesc, m_wFlags3AndTokenRemainder) & 0x3) == 0) {
#else // !BIGENDIAN
if ((offsetof(MethodDesc, m_wFlags3AndTokenRemainder) & 0x3) != 0) {
#endif // !BIGENDIAN
static_assert_no_msg(sizeof(m_wFlags3AndTokenRemainder) == 2);
dwMask <<= 16;
}
#ifdef _PREFAST_
#pragma warning(pop)
#endif
if (fSet)
InterlockedOr(pdwFlags, dwMask);
else
InterlockedAnd(pdwFlags, ~dwMask);
return wOldState;
}
#endif // !DACCESS_COMPILE
//*******************************************************************************
// Returns the address of the native code.
//
// Methods which have no native code are either implemented by stubs or not jitted yet.
// For example, NDirectMethodDesc's have no native code. They are treated as
// implemented by stubs. On WIN64, these stubs are IL stubs, which DO have native code.
//
// This function returns null if the method has no native code.
PCODE MethodDesc::GetNativeCode()
{
WRAPPER_NO_CONTRACT;
SUPPORTS_DAC;
_ASSERTE(!IsDefaultInterfaceMethod() || HasNativeCodeSlot());
if (HasNativeCodeSlot())
{
// When profiler is enabled, profiler may ask to rejit a code even though we
// we have ngen code for this MethodDesc. (See MethodDesc::DoPrestub).
// This means that *ppCode is not stable. It can turn from non-zero to zero.
PTR_PCODE ppCode = GetAddrOfNativeCodeSlot();
PCODE pCode = *ppCode;
#ifdef TARGET_ARM
if (pCode != NULL)
pCode |= THUMB_CODE;
#endif
return pCode;
}
if (!HasStableEntryPoint() || HasPrecode())
return NULL;
return GetStableEntryPoint();
}
PCODE MethodDesc::GetNativeCodeAnyVersion()
{
WRAPPER_NO_CONTRACT;
SUPPORTS_DAC;
PCODE pDefaultCode = GetNativeCode();
if (pDefaultCode != NULL)
{
return pDefaultCode;
}
else
{
CodeVersionManager *pCodeVersionManager = GetCodeVersionManager();
CodeVersionManager::LockHolder codeVersioningLockHolder;
ILCodeVersionCollection ilVersionCollection = pCodeVersionManager->GetILCodeVersions(PTR_MethodDesc(this));
for (ILCodeVersionIterator curIL = ilVersionCollection.Begin(), endIL = ilVersionCollection.End(); curIL != endIL; curIL++)
{
NativeCodeVersionCollection nativeCollection = curIL->GetNativeCodeVersions(PTR_MethodDesc(this));
for (NativeCodeVersionIterator curNative = nativeCollection.Begin(), endNative = nativeCollection.End(); curNative != endNative; curNative++)
{
PCODE native = curNative->GetNativeCode();
if(native != NULL)
{
return native;
}
}
}
return NULL;
}
}
//*******************************************************************************
PTR_PCODE MethodDesc::GetAddrOfNativeCodeSlot()
{
WRAPPER_NO_CONTRACT;
_ASSERTE(HasNativeCodeSlot());
SIZE_T size = s_ClassificationSizeTable[m_wFlags & (mdcClassification | mdcHasNonVtableSlot | mdcMethodImpl)];
return (PTR_PCODE)(dac_cast<TADDR>(this) + size);
}
//*******************************************************************************
BOOL MethodDesc::IsVoid()
{
WRAPPER_NO_CONTRACT;
MetaSig sig(this);
return sig.IsReturnTypeVoid();
}
//*******************************************************************************
BOOL MethodDesc::HasRetBuffArg()
{
WRAPPER_NO_CONTRACT;
MetaSig sig(this);
ArgIterator argit(&sig);
return argit.HasRetBuffArg();
}
//*******************************************************************************
// This returns the offset of the IL.
// The offset is relative to the base of the IL image.
ULONG MethodDesc::GetRVA()
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
FORBID_FAULT;
SUPPORTS_DAC;
}
CONTRACTL_END
if (IsRuntimeSupplied())
{
return 0;
}
// Methods without metadata don't have an RVA. Examples are IL stubs and LCG methods.
if (IsNoMetadata())
{
return 0;
}
if (GetMemberDef() & 0x00FFFFFF)
{
Module *pModule = GetModule();
PREFIX_ASSUME(pModule != NULL);
DWORD dwDescrOffset;
DWORD dwImplFlags;
if (FAILED(pModule->GetMDImport()->GetMethodImplProps(GetMemberDef(), &dwDescrOffset, &dwImplFlags)))
{ // Class loader already asked for MethodImpls, so this should always succeed (unless there's a
// bug or a new code path)
_ASSERTE(!"If this ever fires, then this method should return HRESULT");
return 0;
}
BAD_FORMAT_NOTHROW_ASSERT(IsNDirect() || IsMiIL(dwImplFlags) || IsMiOPTIL(dwImplFlags) || dwDescrOffset == 0);
return dwDescrOffset;
}
return 0;
}
//*******************************************************************************
BOOL MethodDesc::IsVarArg()
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;
SUPPORTS_DAC;
Signature signature = GetSignature();
_ASSERTE(!signature.IsEmpty());
return MetaSig::IsVarArg(signature);
}
//*******************************************************************************
COR_ILMETHOD* MethodDesc::GetILHeader(BOOL fAllowOverrides /*=FALSE*/)
{
CONTRACTL
{
THROWS;
GC_NOTRIGGER;
PRECONDITION(IsIL());
PRECONDITION(!IsUnboxingStub());
}
CONTRACTL_END
Module *pModule = GetModule();
// Always pickup 'permanent' overrides like reflection emit, EnC, etc.
// but only grab temporary overrides (like profiler rewrites) if asked to
TADDR pIL = pModule->GetDynamicIL(GetMemberDef(), fAllowOverrides);
if (pIL == NULL)
{
pIL = pModule->GetIL(GetRVA());
}
#ifdef _DEBUG_IMPL
if (pIL != NULL)
{
//
// This is convenient place to verify that COR_ILMETHOD_DECODER::GetOnDiskSize is in sync
// with our private DACized copy in PEDecoder::ComputeILMethodSize
//
COR_ILMETHOD_DECODER header((COR_ILMETHOD *)pIL);
SIZE_T size1 = header.GetOnDiskSize((COR_ILMETHOD *)pIL);
SIZE_T size2 = PEDecoder::ComputeILMethodSize(pIL);
_ASSERTE(size1 == size2);
}
#endif
#ifdef DACCESS_COMPILE
return (pIL != NULL) ? DacGetIlMethod(pIL) : NULL;
#else // !DACCESS_COMPILE
return PTR_COR_ILMETHOD(pIL);
#endif // !DACCESS_COMPILE
}
//*******************************************************************************
ReturnKind MethodDesc::ParseReturnKindFromSig(INDEBUG(bool supportStringConstructors))
{
CONTRACTL
{
if (FORBIDGC_LOADER_USE_ENABLED()) NOTHROW; else THROWS;
GC_NOTRIGGER;
FORBID_FAULT;
}
CONTRACTL_END
ENABLE_FORBID_GC_LOADER_USE_IN_THIS_SCOPE();
TypeHandle thValueType;
MetaSig sig(this);
CorElementType et = sig.GetReturnTypeNormalized(&thValueType);
switch (et)
{
case ELEMENT_TYPE_STRING:
case ELEMENT_TYPE_CLASS:
case ELEMENT_TYPE_SZARRAY:
case ELEMENT_TYPE_ARRAY:
case ELEMENT_TYPE_OBJECT:
case ELEMENT_TYPE_VAR:
return RT_Object;
#ifdef ENREGISTERED_RETURNTYPE_INTEGER_MAXSIZE
case ELEMENT_TYPE_VALUETYPE:
// We return value types in registers if they fit in ENREGISTERED_RETURNTYPE_MAXSIZE
// These valuetypes could contain gc refs.
{
ArgIterator argit(&sig);
if (!argit.HasRetBuffArg())
{
// the type must already be loaded
_ASSERTE(!thValueType.IsNull());
if (!thValueType.IsTypeDesc())
{
MethodTable * pReturnTypeMT = thValueType.AsMethodTable();
#ifdef UNIX_AMD64_ABI
if (pReturnTypeMT->IsRegPassedStruct())
{
// The Multi-reg return case using the classhandle is only implemented for AMD64 SystemV ABI.
// On other platforms, multi-reg return is not supported with GcInfo v1.
// So, the relevant information must be obtained from the GcInfo tables (which requires version2).
EEClass* eeClass = pReturnTypeMT->GetClass();
ReturnKind regKinds[2] = { RT_Unset, RT_Unset };
int orefCount = 0;
for (int i = 0; i < 2; i++)
{
if (eeClass->GetEightByteClassification(i) == SystemVClassificationTypeIntegerReference)
{
regKinds[i] = RT_Object;
}
else if (eeClass->GetEightByteClassification(i) == SystemVClassificationTypeIntegerByRef)
{
regKinds[i] = RT_ByRef;
}
else
{
regKinds[i] = RT_Scalar;
}
}
ReturnKind structReturnKind = GetStructReturnKind(regKinds[0], regKinds[1]);
return structReturnKind;
}
#endif // UNIX_AMD64_ABI
if (pReturnTypeMT->ContainsPointers() || pReturnTypeMT->IsByRefLike())
{
if (pReturnTypeMT->GetNumInstanceFields() == 1)
{
_ASSERTE(pReturnTypeMT->GetNumInstanceFieldBytes() == sizeof(void*));
// Note: we can't distinguish RT_Object from RT_ByRef, the caller has to tolerate that.
return RT_Object;
}
else
{
// Multi reg return case with pointers, can't restore the actual kind.
return RT_Illegal;
}
}
}
}
}
break;
#endif // ENREGISTERED_RETURNTYPE_INTEGER_MAXSIZE
#ifdef _DEBUG
case ELEMENT_TYPE_VOID:
// String constructors return objects. We should not have any ecall string
// constructors, except when called from gc coverage codes (which is only
// done under debug). We will therefore optimize the retail version of this
// method to not support string constructors.
if (IsCtor() && GetMethodTable()->HasComponentSize())
{
_ASSERTE(supportStringConstructors);
return RT_Object;
}
break;
#endif // _DEBUG
case ELEMENT_TYPE_BYREF:
return RT_ByRef;
default:
break;
}
return RT_Scalar;
}
ReturnKind MethodDesc::GetReturnKind(INDEBUG(bool supportStringConstructors))
{
// For simplicity, we don't hijack in funclets, but if you ever change that,
// be sure to choose the OnHijack... callback type to match that of the FUNCLET
// not the main method (it would probably be Scalar).
ENABLE_FORBID_GC_LOADER_USE_IN_THIS_SCOPE();
// Mark that we are performing a stackwalker like operation on the current thread.
// This is necessary to allow the signature parsing functions to work without triggering any loads
StackWalkerWalkingThreadHolder threadStackWalking(GetThread());
#ifdef TARGET_X86
MetaSig msig(this);
if (msig.HasFPReturn())
{
// Figuring out whether the function returns FP or not is hard to do
// on-the-fly, so we use a different callback helper on x86 where this
// piece of information is needed in order to perform the right save &
// restore of the return value around the call to OnHijackScalarWorker.
return RT_Float;
}
#endif // TARGET_X86
return ParseReturnKindFromSig(INDEBUG(supportStringConstructors));
}
#ifdef FEATURE_COMINTEROP
#ifndef DACCESS_COMPILE
//*******************************************************************************
LONG MethodDesc::GetComDispid()
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END
ULONG dispid = -1;
HRESULT hr = GetMDImport()->GetDispIdOfMemberDef(
GetMemberDef(), // The member for which to get props.
&dispid // return dispid.
);
if (FAILED(hr))
return -1;
return (LONG)dispid;
}
//*******************************************************************************
WORD MethodDesc::GetComSlot()
{
CONTRACTL
{
THROWS;
GC_NOTRIGGER;
FORBID_FAULT;
}
CONTRACTL_END
MethodTable * pMT = GetMethodTable();
_ASSERTE(pMT->IsInterface());
// COM slots are biased from MethodTable slots depending on interface type
WORD numExtraSlots = ComMethodTable::GetNumExtraSlots(pMT->GetComInterfaceType());
// Normal interfaces are laid out the same way as in the MethodTable, while
// sparse interfaces need to go through an extra layer of mapping.
WORD slot;
if (pMT->IsSparseForCOMInterop())
slot = numExtraSlots + pMT->GetClass()->GetSparseCOMInteropVTableMap()->LookupVTSlot(GetSlot());
else
slot = numExtraSlots + GetSlot();
return slot;
}
#endif // !DACCESS_COMPILE
#endif // FEATURE_COMINTEROP
//*******************************************************************************
DWORD MethodDesc::GetAttrs() const
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END
if (IsArray())
return dac_cast<PTR_ArrayMethodDesc>(this)->GetAttrs();
else if (IsNoMetadata())
return dac_cast<PTR_DynamicMethodDesc>(this)->GetAttrs();
DWORD dwAttributes;
if (FAILED(GetMDImport()->GetMethodDefProps(GetMemberDef(), &dwAttributes)))
{ // Class loader already asked for attributes, so this should always succeed (unless there's a
// bug or a new code path)
_ASSERTE(!"If this ever fires, then this method should return HRESULT");
return 0;
}
return dwAttributes;
}
//*******************************************************************************
DWORD MethodDesc::GetImplAttrs()
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END
DWORD props;
if (FAILED(GetMDImport()->GetMethodImplProps(GetMemberDef(), NULL, &props)))
{ // Class loader already asked for MethodImpls, so this should always succeed (unless there's a
// bug or a new code path)
_ASSERTE(!"If this ever fires, then this method should return HRESULT");
return 0;
}
return props;
}
//*******************************************************************************
Module* MethodDesc::GetLoaderModule()
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;
if (HasMethodInstantiation() && !IsGenericMethodDefinition())
{
Module *retVal = ClassLoader::ComputeLoaderModule(GetMethodTable(),
GetMemberDef(),
GetMethodInstantiation());
return retVal;
}
else
{
return GetMethodTable()->GetLoaderModule();
}
}
//*******************************************************************************
Module *MethodDesc::GetModule() const
{
STATIC_CONTRACT_NOTHROW;
STATIC_CONTRACT_GC_NOTRIGGER;
STATIC_CONTRACT_FORBID_FAULT;
SUPPORTS_DAC;
MethodTable* pMT = GetMethodDescChunk()->GetMethodTable();
return pMT->GetModule();
}
//*******************************************************************************
// Is this an instantiating stub for generics? This does not include those
// BoxedEntryPointStubs which call an instantiating stub.
BOOL MethodDesc::IsInstantiatingStub()
{
WRAPPER_NO_CONTRACT;
SUPPORTS_DAC;
return
(GetClassification() == mcInstantiated)
&& !IsUnboxingStub()
&& AsInstantiatedMethodDesc()->IMD_IsWrapperStubWithInstantiations();
}
//*******************************************************************************
BOOL MethodDesc::IsWrapperStub()
{
WRAPPER_NO_CONTRACT;
SUPPORTS_DAC;
return (IsUnboxingStub() || IsInstantiatingStub());
}
#ifndef DACCESS_COMPILE
//*******************************************************************************
MethodDesc *MethodDesc::GetWrappedMethodDesc()
{
WRAPPER_NO_CONTRACT;
_ASSERTE(IsWrapperStub());
if (IsUnboxingStub())
{
return this->GetMethodTable()->GetUnboxedEntryPointMD(this);
}
if (IsInstantiatingStub())
{
MethodDesc *pRet = AsInstantiatedMethodDesc()->IMD_GetWrappedMethodDesc();
#ifdef _DEBUG
MethodDesc *pAltMD =
MethodDesc::FindOrCreateAssociatedMethodDesc(this,
this->GetMethodTable(),
FALSE, /* no unboxing entrypoint */
this->GetMethodInstantiation(),
TRUE /* get shared code */ );
_ASSERTE(pAltMD == pRet);
#endif // _DEBUG
return pRet;
}
return NULL;
}
MethodDesc *MethodDesc::GetExistingWrappedMethodDesc()
{
CONTRACTL
{
THROWS;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;
_ASSERTE(IsWrapperStub());
if (IsUnboxingStub())
{
return this->GetMethodTable()->GetExistingUnboxedEntryPointMD(this);
}
if (IsInstantiatingStub())
{
MethodDesc *pRet = AsInstantiatedMethodDesc()->IMD_GetWrappedMethodDesc();
return pRet;
}
return NULL;
}
#endif // !DACCESS_COMPILE
//*******************************************************************************
BOOL MethodDesc::IsSharedByGenericInstantiations()
{
LIMITED_METHOD_DAC_CONTRACT;
if (IsWrapperStub())
return FALSE;
else if (GetMethodTable()->IsSharedByGenericInstantiations())
return TRUE;
else return IsSharedByGenericMethodInstantiations();
}
//*******************************************************************************
BOOL MethodDesc::IsSharedByGenericMethodInstantiations()
{
LIMITED_METHOD_DAC_CONTRACT;
if (GetClassification() == mcInstantiated)
return AsInstantiatedMethodDesc()->IMD_IsSharedByGenericMethodInstantiations();
else return FALSE;
}
//*******************************************************************************
// Does this method require an extra MethodTable argument for instantiation information?
// This is the case for
// * per-inst static methods in shared-code instantiated generic classes (e.g. static void MyClass<string>::m())
// - there is no this pointer providing generic dictionary info
// * shared-code instance methods in instantiated generic structs (e.g. void MyValueType<string>::m())
// - unboxed 'this' pointer in value-type instance methods don't have MethodTable pointer by definition
// * shared instance and default interface methods called via interface dispatch (e. g. IFoo<string>.Foo calling into IFoo<object>::Foo())
// - this pointer is ambiguous as it can implement more than one IFoo<T>
BOOL MethodDesc::RequiresInstMethodTableArg()
{
LIMITED_METHOD_DAC_CONTRACT;
return
IsSharedByGenericInstantiations() &&
!HasMethodInstantiation() &&
(IsStatic() || GetMethodTable()->IsValueType() || (GetMethodTable()->IsInterface() && !IsAbstract()));
}
//*******************************************************************************
// Does this method require an extra InstantiatedMethodDesc argument for instantiation information?
// This is the case for
// * shared-code instantiated generic methods
BOOL MethodDesc::RequiresInstMethodDescArg()
{
LIMITED_METHOD_DAC_CONTRACT;
return IsSharedByGenericInstantiations() &&
HasMethodInstantiation();
}
//*******************************************************************************
// Does this method require any kind of extra argument for instantiation information?
BOOL MethodDesc::RequiresInstArg()
{
LIMITED_METHOD_DAC_CONTRACT;
BOOL fRet = IsSharedByGenericInstantiations() &&
(HasMethodInstantiation() || IsStatic() || GetMethodTable()->IsValueType() || (GetMethodTable()->IsInterface() && !IsAbstract()));
_ASSERT(fRet == (RequiresInstMethodTableArg() || RequiresInstMethodDescArg()));
return fRet;
}
//*******************************************************************************
BOOL MethodDesc::IsRuntimeMethodHandle()
{
WRAPPER_NO_CONTRACT;
// <TODO> Refine this check further for BoxedEntryPointStubs </TODO>
return (!HasMethodInstantiation() || !IsSharedByGenericMethodInstantiations());
}
//*******************************************************************************
// Strip off method and class instantiation if present e.g.
// C1<int>.m1<string> -> C1.m1
// C1<int>.m2 -> C1.m2
// C2.m2<int> -> C2.m2
// C2.m2 -> C2.m2
MethodDesc* MethodDesc::LoadTypicalMethodDefinition()
{
CONTRACT(MethodDesc*)
{
THROWS;
GC_TRIGGERS;
INJECT_FAULT(COMPlusThrowOM(););
POSTCONDITION(CheckPointer(RETVAL));
POSTCONDITION(RETVAL->IsTypicalMethodDefinition());
}
CONTRACT_END
#ifndef DACCESS_COMPILE
if (HasClassOrMethodInstantiation())
{
MethodTable* pMT = GetMethodTable();
if (!pMT->IsTypicalTypeDefinition())
{
MethodTable* pMTTypical = ClassLoader::LoadTypeDefThrowing(pMT->GetModule(),
pMT->GetCl(),
ClassLoader::ThrowIfNotFound,
ClassLoader::PermitUninstDefOrRef).GetMethodTable();
LOG((LF_CLASSLOADER, LL_INFO100000, "MD:LTMD: pMT:%p => pMTTypical:%p\n",
pMT, pMTTypical));
pMT = pMTTypical;
}
CONSISTENCY_CHECK(TypeHandle(pMT).CheckFullyLoaded());
MethodDesc *resultMD = pMT->GetParallelMethodDesc(this);
PREFIX_ASSUME(resultMD != NULL);
resultMD->CheckRestore();
RETURN (resultMD);
}
else
#endif // !DACCESS_COMPILE
{
RETURN(this);
}
}
//*******************************************************************************
BOOL MethodDesc::IsTypicalMethodDefinition() const
{
LIMITED_METHOD_CONTRACT;
if (HasMethodInstantiation() && !IsGenericMethodDefinition())
return FALSE;
if (HasClassInstantiation() && !GetMethodTable()->IsGenericTypeDefinition())
return FALSE;
return TRUE;
}
//*******************************************************************************
BOOL MethodDesc::AcquiresInstMethodTableFromThis() {
LIMITED_METHOD_CONTRACT;
SUPPORTS_DAC;
return
IsSharedByGenericInstantiations() &&
!HasMethodInstantiation() &&
!IsStatic() &&
!GetMethodTable()->IsValueType() &&
!(GetMethodTable()->IsInterface() && !IsAbstract());
}
//*******************************************************************************
UINT MethodDesc::SizeOfArgStack()
{
WRAPPER_NO_CONTRACT;
MetaSig msig(this);
ArgIterator argit(&msig);
return argit.SizeOfArgStack();
}
UINT MethodDesc::SizeOfNativeArgStack()
{
#ifndef UNIX_AMD64_ABI
return SizeOfArgStack();
#else
WRAPPER_NO_CONTRACT;
MetaSig msig(this);
PInvokeArgIterator argit(&msig);
return argit.SizeOfArgStack();
#endif
}
#ifdef TARGET_X86
//*******************************************************************************
UINT MethodDesc::CbStackPop()
{
WRAPPER_NO_CONTRACT;
SUPPORTS_DAC;
MetaSig msig(this);
ArgIterator argit(&msig);
bool fCtorOfVariableSizedObject = msig.HasThis() && (GetMethodTable() == g_pStringClass) && IsCtor();
if (fCtorOfVariableSizedObject)
{
msig.ClearHasThis();
}
return argit.CbStackPop();
}
#endif // TARGET_X86
#ifndef DACCESS_COMPILE
//*******************************************************************************
// Strip off the method instantiation (if present) e.g.
// C<int>.m<string> -> C<int>.m
// D.m<string> -> D.m
// Note that this also canonicalizes the owning method table
// @todo check uses and clean this up
MethodDesc* MethodDesc::StripMethodInstantiation()
{
CONTRACT(MethodDesc*)
{
NOTHROW;
GC_NOTRIGGER;
FORBID_FAULT;
POSTCONDITION(CheckPointer(RETVAL));
}
CONTRACT_END
if (!HasClassOrMethodInstantiation())
RETURN(this);
MethodTable *pMT = GetMethodTable()->GetCanonicalMethodTable();
MethodDesc *resultMD = pMT->GetParallelMethodDesc(this);
_ASSERTE(resultMD->IsGenericMethodDefinition() || !resultMD->HasMethodInstantiation());
RETURN(resultMD);
}
//*******************************************************************************
MethodDescChunk *MethodDescChunk::CreateChunk(LoaderHeap *pHeap, DWORD methodDescCount,
DWORD classification, BOOL fNonVtableSlot, BOOL fNativeCodeSlot, MethodTable *pInitialMT, AllocMemTracker *pamTracker)
{
CONTRACT(MethodDescChunk *)
{
THROWS;
GC_NOTRIGGER;
INJECT_FAULT(ThrowOutOfMemory());
PRECONDITION(CheckPointer(pHeap));
PRECONDITION(CheckPointer(pInitialMT));
PRECONDITION(CheckPointer(pamTracker));
POSTCONDITION(CheckPointer(RETVAL));
}
CONTRACT_END;
SIZE_T oneSize = MethodDesc::GetBaseSize(classification);
if (fNonVtableSlot)
oneSize += sizeof(MethodDesc::NonVtableSlot);
if (fNativeCodeSlot)
oneSize += sizeof(MethodDesc::NativeCodeSlot);
_ASSERTE((oneSize & MethodDesc::ALIGNMENT_MASK) == 0);
DWORD maxMethodDescsPerChunk = (DWORD)(MethodDescChunk::MaxSizeOfMethodDescs / oneSize);
// Limit the maximum MethodDescs per chunk by the number of precodes that can fit to a single memory page,
// since we allocate consecutive temporary entry points for all MethodDescs in the whole chunk.
DWORD maxPrecodesPerPage = Precode::GetMaxTemporaryEntryPointsCount();
if (maxPrecodesPerPage < maxMethodDescsPerChunk)
maxMethodDescsPerChunk = maxPrecodesPerPage;
if (methodDescCount == 0)
methodDescCount = maxMethodDescsPerChunk;
MethodDescChunk * pFirstChunk = NULL;
do
{
DWORD count = min(methodDescCount, maxMethodDescsPerChunk);
void * pMem = pamTracker->Track(
pHeap->AllocMem(S_SIZE_T(sizeof(TADDR) + sizeof(MethodDescChunk) + oneSize * count)));
// Skip pointer to temporary entrypoints
MethodDescChunk * pChunk = (MethodDescChunk *)((BYTE*)pMem + sizeof(TADDR));
pChunk->SetSizeAndCount(oneSize * count, count);
pChunk->SetMethodTable(pInitialMT);
MethodDesc * pMD = pChunk->GetFirstMethodDesc();
for (DWORD i = 0; i < count; i++)
{
pMD->SetChunkIndex(pChunk);
pMD->SetMethodDescIndex(i);
pMD->SetClassification(classification);
if (fNonVtableSlot)
pMD->SetHasNonVtableSlot();
if (fNativeCodeSlot)
pMD->SetHasNativeCodeSlot();
_ASSERTE(pMD->SizeOf() == oneSize);
pMD = (MethodDesc *)((BYTE *)pMD + oneSize);
}
pChunk->m_next = pFirstChunk;
pFirstChunk = pChunk;
methodDescCount -= count;
}
while (methodDescCount > 0);
RETURN pFirstChunk;
}
//--------------------------------------------------------------------
// Virtual Resolution on Objects
//
// Given a MethodDesc and an Object, return the target address
// and/or the target MethodDesc and/or make a call.
//
// Some of the implementation of this logic is in
// MethodTable::GetMethodDescForInterfaceMethodAndServer.
// Those functions should really be moved here.
//--------------------------------------------------------------------
//*******************************************************************************
// The following resolve virtual dispatch for the given method on the given
// object down to an actual address to call, including any
// handling of context proxies and other thunking layers.
MethodDesc* MethodDesc::ResolveGenericVirtualMethod(OBJECTREF *orThis)
{
CONTRACT(MethodDesc *)
{
THROWS;
GC_TRIGGERS;
PRECONDITION(IsVtableMethod());
PRECONDITION(HasMethodInstantiation());
PRECONDITION(!ContainsGenericVariables());
POSTCONDITION(CheckPointer(RETVAL));
POSTCONDITION(RETVAL->HasMethodInstantiation());
}
CONTRACT_END;
// Method table of target (might be instantiated)
MethodTable *pObjMT = (*orThis)->GetMethodTable();
// This is the static method descriptor describing the call.
// It is not the destination of the call, which we must compute.
MethodDesc* pStaticMD = this;
// Strip off the method instantiation if present
MethodDesc* pStaticMDWithoutGenericMethodArgs = pStaticMD->StripMethodInstantiation();
// Compute the target, though we have not yet applied the type arguments.
MethodDesc *pTargetMDBeforeGenericMethodArgs =
pStaticMD->IsInterface()
? MethodTable::GetMethodDescForInterfaceMethodAndServer(TypeHandle(pStaticMD->GetMethodTable()),
pStaticMDWithoutGenericMethodArgs,orThis)
: pObjMT->GetMethodDescForSlot(pStaticMDWithoutGenericMethodArgs->GetSlot());
pTargetMDBeforeGenericMethodArgs->CheckRestore();
// The actual destination may lie anywhere in the inheritance hierarchy.
// between the static descriptor and the target object.
// So now compute where we are really going! This may be an instantiated
// class type if the generic virtual lies in a generic class.
MethodTable *pTargetMT = pTargetMDBeforeGenericMethodArgs->GetMethodTable();
// No need to find/create a new generic instantiation if the target is the
// same as the static, i.e. the virtual method has not been overridden.
if (!pTargetMT->IsSharedByGenericInstantiations() && !pTargetMT->IsValueType() &&
pTargetMDBeforeGenericMethodArgs == pStaticMDWithoutGenericMethodArgs)
RETURN(pStaticMD);
if (pTargetMT->IsSharedByGenericInstantiations())
{
pTargetMT = ClassLoader::LoadGenericInstantiationThrowing(pTargetMT->GetModule(),
pTargetMT->GetCl(),
pTargetMDBeforeGenericMethodArgs->GetExactClassInstantiation(TypeHandle(pObjMT))).GetMethodTable();
}
RETURN(MethodDesc::FindOrCreateAssociatedMethodDesc(
pTargetMDBeforeGenericMethodArgs,
pTargetMT,
(pTargetMT->IsValueType()), /* get unboxing entry point if a struct*/
pStaticMD->GetMethodInstantiation(),
FALSE /* no allowInstParam */ ));
}
//*******************************************************************************
PCODE MethodDesc::GetSingleCallableAddrOfVirtualizedCode(OBJECTREF *orThis, TypeHandle staticTH)
{
WRAPPER_NO_CONTRACT;
PRECONDITION(IsVtableMethod());
MethodTable *pObjMT = (*orThis)->GetMethodTable();
if (HasMethodInstantiation())
{
CheckRestore();
MethodDesc *pResultMD = ResolveGenericVirtualMethod(orThis);
// If we're remoting this call we can't call directly on the returned
// method desc, we need to go through a stub that guarantees we end up
// in the remoting handler. The stub we use below is normally just for
// non-virtual calls on virtual methods (that have the same problem
// where we could end up bypassing the remoting system), but it serves
// our purpose here (basically pushes our correctly instantiated,
// resolved method desc on the stack and calls the remoting code).
return pResultMD->GetSingleCallableAddrOfCode();
}
if (IsInterface())
{
MethodDesc * pTargetMD = MethodTable::GetMethodDescForInterfaceMethodAndServer(staticTH,this,orThis);
return pTargetMD->GetSingleCallableAddrOfCode();
}
return pObjMT->GetRestoredSlot(GetSlot());
}
//*******************************************************************************
// The following resolve virtual dispatch for the given method on the given
// object down to an actual address to call, including any
// handling of context proxies and other thunking layers.
PCODE MethodDesc::GetMultiCallableAddrOfVirtualizedCode(OBJECTREF *orThis, TypeHandle staticTH)
{
CONTRACT(PCODE)
{
THROWS;
GC_TRIGGERS;
PRECONDITION(IsVtableMethod());
POSTCONDITION(RETVAL != NULL);
}
CONTRACT_END;
// Method table of target (might be instantiated)
MethodTable *pObjMT = (*orThis)->GetMethodTable();
// This is the static method descriptor describing the call.
// It is not the destination of the call, which we must compute.
MethodDesc* pStaticMD = this;
MethodDesc *pTargetMD;
if (pStaticMD->HasMethodInstantiation())
{
CheckRestore();
pTargetMD = ResolveGenericVirtualMethod(orThis);
// If we're remoting this call we can't call directly on the returned
// method desc, we need to go through a stub that guarantees we end up
// in the remoting handler. The stub we use below is normally just for
// non-virtual calls on virtual methods (that have the same problem
// where we could end up bypassing the remoting system), but it serves
// our purpose here (basically pushes our correctly instantiated,
// resolved method desc on the stack and calls the remoting code).
RETURN(pTargetMD->GetMultiCallableAddrOfCode());
}
if (pStaticMD->IsInterface())
{
pTargetMD = MethodTable::GetMethodDescForInterfaceMethodAndServer(staticTH,pStaticMD,orThis);
RETURN(pTargetMD->GetMultiCallableAddrOfCode());
}
pTargetMD = pObjMT->GetMethodDescForSlot(pStaticMD->GetSlot());
RETURN (pTargetMD->GetMultiCallableAddrOfCode());
}
//*******************************************************************************
PCODE MethodDesc::GetMultiCallableAddrOfCode(CORINFO_ACCESS_FLAGS accessFlags /*=CORINFO_ACCESS_LDFTN*/)
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
INJECT_FAULT(COMPlusThrowOM());
}
CONTRACTL_END
PCODE ret = TryGetMultiCallableAddrOfCode(accessFlags);
if (ret == NULL)
{
GCX_COOP();
// We have to allocate funcptr stub
ret = GetLoaderAllocator()->GetFuncPtrStubs()->GetFuncPtrStub(this);
}
return ret;
}
//*******************************************************************************
//
// Returns a callable entry point for a function.
// Multiple entry points could be used for a single function.
// ie. this function is not idempotent
//
// We must ensure that GetMultiCallableAddrOfCode works
// correctly for all of the following cases:
// 1. shared generic method instantiations
// 2. unshared generic method instantiations
// 3. instance methods in shared generic classes
// 4. instance methods in unshared generic classes
// 5. static methods in shared generic classes.
// 6. static methods in unshared generic classes.
//
// For case 1 and 5 the methods are implemented using
// an instantiating stub (i.e. IsInstantiatingStub()
// should be true). These stubs pass on to
// shared-generic-code-which-requires-an-extra-type-context-parameter.
// So whenever we use LDFTN on these we need to give out
// the address of an instantiating stub.
//
// For cases 2, 3, 4 and 6 we can just use the standard technique for LdFtn:
// (for 2 we give out the address of the fake "slot" in InstantiatedMethodDescs)
// (for 3 it doesn't matter if the code is shared between instantiations
// because the instantiation context is picked up from the "this" parameter.)
PCODE MethodDesc::TryGetMultiCallableAddrOfCode(CORINFO_ACCESS_FLAGS accessFlags)
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
INJECT_FAULT(COMPlusThrowOM());
}
CONTRACTL_END
if (IsGenericMethodDefinition())
{
_ASSERTE(!"Cannot take the address of an uninstantiated generic method.");
COMPlusThrow(kInvalidProgramException);
}
if (accessFlags & CORINFO_ACCESS_LDFTN)
{
// Whenever we use LDFTN on shared-generic-code-which-requires-an-extra-parameter
// we need to give out the address of an instantiating stub. This is why we give
// out GetStableEntryPoint() for the IsInstantiatingStub() case: this is
// safe. But first we assert that we only use GetMultiCallableAddrOfCode on
// the instantiating stubs and not on the shared code itself.
_ASSERTE(!RequiresInstArg());
_ASSERTE(!IsSharedByGenericMethodInstantiations());
// No other access flags are valid with CORINFO_ACCESS_LDFTN
_ASSERTE((accessFlags & ~CORINFO_ACCESS_LDFTN) == 0);
}
// We create stable entrypoints for these upfront
if (IsWrapperStub() || IsEnCAddedMethod())
return GetStableEntryPoint();
// For EnC always just return the stable entrypoint so we can update the code
if (InEnCEnabledModule())
return GetStableEntryPoint();
// If the method has already been jitted, we can give out the direct address
// Note that we may have previously created a FuncPtrStubEntry, but
// GetMultiCallableAddrOfCode() does not need to be idempotent.
if (IsFCall())
{
// Call FCalls directly when possible
if (!IsInterface() && !GetMethodTable()->ContainsGenericVariables())
{
BOOL fSharedOrDynamicFCallImpl;
PCODE pFCallImpl = ECall::GetFCallImpl(this, &fSharedOrDynamicFCallImpl);
if (!fSharedOrDynamicFCallImpl)
return pFCallImpl;
// Fake ctors share one implementation that has to be wrapped by prestub
GetOrCreatePrecode();
}
}
else
{
if (IsPointingToStableNativeCode())
return GetNativeCode();
}
if (HasStableEntryPoint())
return GetStableEntryPoint();
if (IsVersionableWithVtableSlotBackpatch())
{
// Caller has to call via slot or allocate funcptr stub
return NULL;
}
// Force the creation of the precode if we would eventually got one anyway
if (MayHavePrecode())
return GetOrCreatePrecode()->GetEntryPoint();
#ifdef HAS_COMPACT_ENTRYPOINTS
// Caller has to call via slot or allocate funcptr stub
return NULL;
#else // HAS_COMPACT_ENTRYPOINTS
//
// Embed call to the temporary entrypoint into the code. It will be patched
// to point to the actual code later.
//
return GetTemporaryEntryPoint();
#endif // HAS_COMPACT_ENTRYPOINTS
}
//*******************************************************************************
PCODE MethodDesc::GetCallTarget(OBJECTREF* pThisObj, TypeHandle ownerType)
{
CONTRACTL
{
THROWS; // Resolving a generic virtual method can throw
GC_TRIGGERS;
MODE_COOPERATIVE;
}
CONTRACTL_END
PCODE pTarget;
if (IsVtableMethod() && !GetMethodTable()->IsValueType())
{
CONSISTENCY_CHECK(NULL != pThisObj);
if (ownerType.IsNull())
ownerType = GetMethodTable();
pTarget = GetSingleCallableAddrOfVirtualizedCode(pThisObj, ownerType);
}
else
{
pTarget = GetSingleCallableAddrOfCode();
}
return pTarget;
}
MethodDesc* NonVirtualEntry2MethodDesc(PCODE entryPoint)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END
RangeSection* pRS = ExecutionManager::FindCodeRange(entryPoint, ExecutionManager::GetScanFlags());
if (pRS == NULL)
{
// Is it an FCALL?
MethodDesc* pFCallMD = ECall::MapTargetBackToMethod(entryPoint);
if (pFCallMD != NULL)
{
return pFCallMD;
}
return NULL;
}
// Inlined fast path for fixup precode and stub precode from RangeList implementation
if (pRS->_flags == RangeSection::RANGE_SECTION_RANGELIST)
{
if (pRS->_pRangeList->GetCodeBlockKind() == STUB_CODE_BLOCK_FIXUPPRECODE)
{
return (MethodDesc*)((FixupPrecode*)PCODEToPINSTR(entryPoint))->GetMethodDesc();
}
if (pRS->_pRangeList->GetCodeBlockKind() == STUB_CODE_BLOCK_STUBPRECODE)
{
return (MethodDesc*)((StubPrecode*)PCODEToPINSTR(entryPoint))->GetMethodDesc();
}
}
MethodDesc* pMD;
if (pRS->_pjit->JitCodeToMethodInfo(pRS, entryPoint, &pMD, NULL))
return pMD;
auto stubCodeBlockKind = pRS->_pjit->GetStubCodeBlockKind(pRS, entryPoint);
switch(stubCodeBlockKind)
{
case STUB_CODE_BLOCK_PRECODE:
return MethodDesc::GetMethodDescFromStubAddr(entryPoint);
case STUB_CODE_BLOCK_FIXUPPRECODE:
return (MethodDesc*)((FixupPrecode*)PCODEToPINSTR(entryPoint))->GetMethodDesc();
case STUB_CODE_BLOCK_STUBPRECODE:
return (MethodDesc*)((StubPrecode*)PCODEToPINSTR(entryPoint))->GetMethodDesc();
default:
// We should never get here
_ASSERTE(!"NonVirtualEntry2MethodDesc failed for RangeSection");
return NULL;
}
}
//*******************************************************************************
// convert an entry point into a method desc
MethodDesc* Entry2MethodDesc(PCODE entryPoint, MethodTable *pMT)
{
CONTRACT(MethodDesc*)
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
POSTCONDITION(RETVAL->SanityCheck());
}
CONTRACT_END
MethodDesc* pMD = NonVirtualEntry2MethodDesc(entryPoint);
if (pMD != NULL)
RETURN(pMD);
pMD = VirtualCallStubManagerManager::Entry2MethodDesc(entryPoint, pMT);
if (pMD != NULL)
RETURN(pMD);
// We should never get here
_ASSERTE(!"Entry2MethodDesc failed");
RETURN (NULL);
}
//*******************************************************************************
BOOL MethodDesc::IsPointingToPrestub()
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;
if (!HasStableEntryPoint())
{
if (IsVersionableWithVtableSlotBackpatch())
{
return GetMethodEntryPoint() == GetTemporaryEntryPoint();
}
return TRUE;
}
if (!HasPrecode())
return FALSE;
return GetPrecode()->IsPointingToPrestub();
}
//*******************************************************************************
void MethodDesc::Reset()
{
WRAPPER_NO_CONTRACT;
// This method is not thread-safe since we are updating
// different pieces of data non-atomically.
// Use this only if you can guarantee thread-safety somehow.
_ASSERTE(InEnCEnabledModule() || // The process is frozen by the debugger
IsDynamicMethod() || // These are used in a very restricted way
GetLoaderModule()->IsReflection()); // Rental methods
// Reset any flags relevant to the old code
ClearFlagsOnUpdate();
if (HasPrecode())
{
GetPrecode()->Reset();
}
else
{
// We should go here only for the rental methods
_ASSERTE(GetLoaderModule()->IsReflection());
InterlockedUpdateFlags3(enum_flag3_HasStableEntryPoint | enum_flag3_HasPrecode, FALSE);
*GetAddrOfSlot() = GetTemporaryEntryPoint();
}
if (HasNativeCodeSlot())
{
*GetAddrOfNativeCodeSlot() = NULL;
}
_ASSERTE(!HasNativeCode());
}
//*******************************************************************************
Dictionary* MethodDesc::GetMethodDictionary()
{
WRAPPER_NO_CONTRACT;
return
(GetClassification() == mcInstantiated)
? (Dictionary*) (AsInstantiatedMethodDesc()->IMD_GetMethodDictionary())
: NULL;
}
//*******************************************************************************
DictionaryLayout* MethodDesc::GetDictionaryLayout()
{
WRAPPER_NO_CONTRACT;
return
((GetClassification() == mcInstantiated) && !IsUnboxingStub())
? AsInstantiatedMethodDesc()->IMD_GetDictionaryLayout()
: NULL;
}
#endif // !DACCESS_COMPILE
//*******************************************************************************
MethodImpl *MethodDesc::GetMethodImpl()
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
FORBID_FAULT;
PRECONDITION(HasMethodImplSlot());
SUPPORTS_DAC;
}
CONTRACTL_END
SIZE_T size = s_ClassificationSizeTable[m_wFlags & (mdcClassification | mdcHasNonVtableSlot)];
return PTR_MethodImpl(dac_cast<TADDR>(this) + size);
}
#ifndef DACCESS_COMPILE
//*******************************************************************************
BOOL MethodDesc::RequiresMethodDescCallingConvention(BOOL fEstimateForChunk /*=FALSE*/)
{
LIMITED_METHOD_CONTRACT;
// Interop marshaling is implemented using shared stubs
if (IsNDirect() || IsComPlusCall())
return TRUE;
return FALSE;
}
//*******************************************************************************
BOOL MethodDesc::RequiresStableEntryPoint(BOOL fEstimateForChunk /*=FALSE*/)
{
LIMITED_METHOD_CONTRACT;
// Create precodes for versionable methods
if (IsVersionableWithPrecode())
return TRUE;
// Create precodes for edit and continue to make methods updateable
if (InEnCEnabledModule() || IsEnCAddedMethod())
return TRUE;
// Precreate precodes for LCG methods so we do not leak memory when the method descs are recycled
if (IsLCGMethod())
return TRUE;
if (fEstimateForChunk)
{
// Make a best guess based on the method table of the chunk.
if (IsInterface())
return TRUE;
}
else
{
// Wrapper stubs are stored in generic dictionary that's not backpatched
if (IsWrapperStub())
return TRUE;
// TODO: Can we avoid early allocation of precodes for interfaces and cominterop?
if ((IsInterface() && !IsStatic() && IsVirtual()) || IsComPlusCall())
return TRUE;
}
return FALSE;
}
#endif // !DACCESS_COMPILE
//*******************************************************************************
BOOL MethodDesc::MayHaveNativeCode()
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END
// This code flow of this method should roughly match the code flow of MethodDesc::DoPrestub.
switch (GetClassification())
{
case mcIL: // IsIL() case. Handled below.
break;
case mcFCall: // FCalls do not have real native code.
return FALSE;
case mcNDirect: // NDirect never have native code (note that the NDirect method
return FALSE; // does not appear as having a native code even for stubs as IL)
case mcEEImpl: // Runtime provided implementation. No native code.
return FALSE;
case mcArray: // Runtime provided implementation. No native code.
return FALSE;
case mcInstantiated: // IsIL() case. Handled below.
break;
#ifdef FEATURE_COMINTEROP
case mcComInterop: // Generated stub. No native code.
return FALSE;
#endif // FEATURE_COMINTEROP
case mcDynamic: // LCG or stub-as-il.
return TRUE;
default:
_ASSERTE(!"Unknown classification");
}
_ASSERTE(IsIL());
if (IsWrapperStub() || ContainsGenericVariables() || IsAbstract())
{
return FALSE;
}
return TRUE;
}
//*******************************************************************************
void MethodDesc::CheckRestore(ClassLoadLevel level)
{
STATIC_CONTRACT_THROWS;
STATIC_CONTRACT_GC_TRIGGERS;
STATIC_CONTRACT_FAULT;
if (!GetMethodTable()->IsFullyLoaded())
{
if (GetClassification() == mcInstantiated)
{
#ifndef DACCESS_COMPILE
InstantiatedMethodDesc *pIMD = AsInstantiatedMethodDesc();
// First restore method table pointer in singleton chunk;
// it might be out-of-module
ClassLoader::EnsureLoaded(TypeHandle(GetMethodTable()), level);
if (ETW_PROVIDER_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER))
{
ETW::MethodLog::MethodRestored(this);
}
#else // DACCESS_COMPILE
DacNotImpl();
#endif // DACCESS_COMPILE
}
else if (IsILStub()) // the only stored-sig MD type that uses ET_INTERNAL
{
ClassLoader::EnsureLoaded(TypeHandle(GetMethodTable()), level);
#ifndef DACCESS_COMPILE
if (ETW_PROVIDER_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER))
{
ETW::MethodLog::MethodRestored(this);
}
#else // DACCESS_COMPILE
DacNotImpl();
#endif // DACCESS_COMPILE
}
else
{
ClassLoader::EnsureLoaded(TypeHandle(GetMethodTable()), level);
}
}
}
// static
MethodDesc* MethodDesc::GetMethodDescFromStubAddr(PCODE addr, BOOL fSpeculative /*=FALSE*/)
{
CONTRACT(MethodDesc *)
{
GC_NOTRIGGER;
NOTHROW;
}
CONTRACT_END;
MethodDesc * pMD = NULL;
#ifdef HAS_COMPACT_ENTRYPOINTS
if (MethodDescChunk::IsCompactEntryPointAtAddress(addr))
{
pMD = MethodDescChunk::GetMethodDescFromCompactEntryPoint(addr, fSpeculative);
RETURN(pMD);
}
#endif // HAS_COMPACT_ENTRYPOINTS
// Otherwise this must be some kind of precode
//
PTR_Precode pPrecode = Precode::GetPrecodeFromEntryPoint(addr, fSpeculative);
PREFIX_ASSUME(fSpeculative || (pPrecode != NULL));
if (pPrecode != NULL)
{
pMD = pPrecode->GetMethodDesc(fSpeculative);
RETURN(pMD);
}
RETURN(NULL); // Not found
}
#ifdef HAS_COMPACT_ENTRYPOINTS
#if defined(TARGET_X86)
#include <pshpack1.h>
static const struct CentralJumpCode {
BYTE m_movzxEAX[3];
BYTE m_shlEAX[3];
BYTE m_addEAX[1];
MethodDesc* m_pBaseMD;
BYTE m_jmp[1];
INT32 m_rel32;
inline void Setup(CentralJumpCode* pCodeRX, MethodDesc* pMD, PCODE target, LoaderAllocator *pLoaderAllocator) {
WRAPPER_NO_CONTRACT;
m_pBaseMD = pMD;
m_rel32 = rel32UsingJumpStub(&pCodeRX->m_rel32, target, pMD, pLoaderAllocator);
}
inline BOOL CheckTarget(TADDR target) {
LIMITED_METHOD_CONTRACT;
TADDR addr = rel32Decode(PTR_HOST_MEMBER_TADDR(CentralJumpCode, this, m_rel32));
return (addr == target);
}
}
c_CentralJumpCode = {
{ 0x0F, 0xB6, 0xC0 }, // movzx eax,al
{ 0xC1, 0xE0, MethodDesc::ALIGNMENT_SHIFT }, // shl eax, MethodDesc::ALIGNMENT_SHIFT
{ 0x05 }, NULL, // add eax, pBaseMD
{ 0xE9 }, 0 // jmp PreStub
};
#include <poppack.h>
#elif defined(TARGET_ARM)
#include <pshpack1.h>
struct CentralJumpCode {
BYTE m_ldrPC[4];
BYTE m_short[2];
MethodDescChunk *m_pChunk;
PCODE m_target;
inline void Setup(PCODE target, MethodDescChunk *pChunk) {
WRAPPER_NO_CONTRACT;
m_target = target;
m_pChunk = pChunk;
}
inline BOOL CheckTarget(TADDR target) {
WRAPPER_NO_CONTRACT;
return ((TADDR)m_target == target);
}
}
c_CentralJumpCode = {
{ 0xDF, 0xF8, 0x08, 0xF0 }, // ldr pc, =pTarget
{ 0x00, 0x00 }, // short offset for alignment
0, // pChunk
0 // pTarget
};
#include <poppack.h>
#else
#error Unsupported platform
#endif
typedef DPTR(struct CentralJumpCode) PTR_CentralJumpCode;
#define TEP_CENTRAL_JUMP_SIZE sizeof(c_CentralJumpCode)
static_assert_no_msg((TEP_CENTRAL_JUMP_SIZE & 1) == 0);
#define TEP_ENTRY_SIZE 4
#ifdef TARGET_ARM
#define TEP_HALF_ENTRY_SIZE (TEP_ENTRY_SIZE / 2)
// Compact entry point on arm consists of two thumb instructions:
// mov r12, pc
// b CentralJumpCode
// First instruction 0x46fc
#define TEP_ENTRY_INSTR1_BYTE1 0xFC
#define TEP_ENTRY_INSTR1_BYTE2 0x46
// Mask for unconditional branch opcode
#define TEP_ENTRY_INSTR2_MASK1 0xE0
// Mask for opcode
#define TEP_ENTRY_INSTR2_MASK2 0xF8
// Bit used for ARM to identify compact entry points
#define COMPACT_ENTRY_ARM_CODE 0x2
/* static */ int MethodDescChunk::GetCompactEntryPointMaxCount ()
{
LIMITED_METHOD_DAC_CONTRACT;
return MAX_OFFSET_UNCONDITIONAL_BRANCH_THUMB / TEP_ENTRY_SIZE;
}
// Get offset from the start of current compact entry point to the CentralJumpCode
static uint16_t DecodeOffsetFromBranchToCentralJump (uint16_t instr)
{
int16_t offset = decodeUnconditionalBranchThumb ((LPBYTE) &instr);
offset += PC_REG_RELATIVE_OFFSET + TEP_HALF_ENTRY_SIZE;
_ASSERTE (offset >= TEP_ENTRY_SIZE && (offset % TEP_ENTRY_SIZE == 0));
return (uint16_t) offset;
}
#ifndef DACCESS_COMPILE
// Encode branch instruction to central jump for current compact entry point
static uint16_t EncodeBranchToCentralJump (int16_t offset)
{
_ASSERTE (offset >= 0 && (offset % TEP_ENTRY_SIZE == 0));
offset += TEP_HALF_ENTRY_SIZE - PC_REG_RELATIVE_OFFSET;
uint16_t instr;
emitUnconditionalBranchThumb ((LPBYTE) &instr, offset);
return instr;
}
#endif // DACCESS_COMPILE
#else // TARGET_ARM
#define TEP_MAX_BEFORE_INDEX (1 + (127 / TEP_ENTRY_SIZE))
#define TEP_MAX_BLOCK_INDEX (TEP_MAX_BEFORE_INDEX + (128 - TEP_CENTRAL_JUMP_SIZE) / TEP_ENTRY_SIZE)
#define TEP_FULL_BLOCK_SIZE (TEP_MAX_BLOCK_INDEX * TEP_ENTRY_SIZE + TEP_CENTRAL_JUMP_SIZE)
#endif // TARGET_ARM
BOOL MethodDescChunk::IsCompactEntryPointAtAddress(PCODE addr)
{
LIMITED_METHOD_DAC_CONTRACT;
#if defined(TARGET_X86) || defined(TARGET_AMD64)
// Compact entrypoints start at odd addresses
return (addr & 1) != 0;
#elif defined(TARGET_ARM)
// Compact entrypoints start at odd addresses (thumb) with second bit set to 1
uint8_t compactEntryPointMask = THUMB_CODE | COMPACT_ENTRY_ARM_CODE;
return (addr & compactEntryPointMask) == compactEntryPointMask;
#else
#error Unsupported platform
#endif
}
//*******************************************************************************
/* static */ MethodDesc* MethodDescChunk::GetMethodDescFromCompactEntryPoint(PCODE addr, BOOL fSpeculative /*=FALSE*/)
{
LIMITED_METHOD_CONTRACT;
#ifdef DACCESS_COMPILE
// Always use speculative checks with DAC
fSpeculative = TRUE;
#endif
// Always do consistency check in debug
if (fSpeculative INDEBUG(|| TRUE))
{
#ifdef TARGET_ARM
TADDR instrCodeAddr = PCODEToPINSTR(addr);
if (!IsCompactEntryPointAtAddress(addr) ||
*PTR_BYTE(instrCodeAddr) != TEP_ENTRY_INSTR1_BYTE1 ||
*PTR_BYTE(instrCodeAddr+1) != TEP_ENTRY_INSTR1_BYTE2)
#else // TARGET_ARM
if ((addr & 3) != 1 ||
*PTR_BYTE(addr) != X86_INSTR_MOV_AL ||
*PTR_BYTE(addr+2) != X86_INSTR_JMP_REL8)
#endif // TARGET_ARM
{
if (fSpeculative) return NULL;
_ASSERTE(!"Unexpected code in temporary entrypoint");
}
}
#ifdef TARGET_ARM
// On ARM compact entry points are thumb
_ASSERTE ((addr & THUMB_CODE) != 0);
addr = addr - THUMB_CODE;
// Get offset for CentralJumpCode from current compact entry point
PTR_UINT16 pBranchInstr = (PTR_UINT16(addr)) + 1;
uint16_t offset = DecodeOffsetFromBranchToCentralJump (*pBranchInstr);
TADDR centralJump = addr + offset;
int index = (centralJump - addr - TEP_ENTRY_SIZE) / TEP_ENTRY_SIZE;
#else // TARGET_ARM
int index = *PTR_BYTE(addr+1);
TADDR centralJump = addr + 4 + *PTR_SBYTE(addr+3);
#endif // TARGET_ARM
CentralJumpCode* pCentralJumpCode = PTR_CentralJumpCode(centralJump);
// Always do consistency check in debug
if (fSpeculative INDEBUG(|| TRUE))
{
SIZE_T i;
for (i = 0; i < TEP_CENTRAL_JUMP_SIZE; i++)
{
BYTE b = ((BYTE*)&c_CentralJumpCode)[i];
if (b != 0 && b != *PTR_BYTE(centralJump+i))
{
if (fSpeculative) return NULL;
_ASSERTE(!"Unexpected code in temporary entrypoint");
}
}
#ifdef TARGET_ARM
_ASSERTE_IMPL(pCentralJumpCode->CheckTarget(GetPreStubCompactARMEntryPoint()));
#else // TARGET_ARM
_ASSERTE_IMPL(pCentralJumpCode->CheckTarget(GetPreStubEntryPoint()));
#endif // TARGET_ARM
}
#ifdef TARGET_ARM
// Go through all MethodDesc in MethodDescChunk and find the one with the required index
PTR_MethodDescChunk pChunk = *((DPTR(PTR_MethodDescChunk))(centralJump + offsetof(CentralJumpCode, m_pChunk)));
TADDR pMD = PTR_HOST_TO_TADDR (pChunk->GetFirstMethodDesc ());
_ASSERTE (index >= 0 && index < ((int) pChunk->GetCount ()));
index = ((int) pChunk->GetCount ()) - 1 - index;
SIZE_T totalSize = 0;
int curIndex = 0;
while (index != curIndex)
{
SIZE_T sizeCur = (PTR_MethodDesc (pMD))->SizeOf ();
totalSize += sizeCur;
pMD += sizeCur;
++curIndex;
}
return PTR_MethodDesc (pMD);
#else // TARGET_ARM
return PTR_MethodDesc((TADDR)pCentralJumpCode->m_pBaseMD + index * MethodDesc::ALIGNMENT);
#endif // TARGET_ARM
}
//*******************************************************************************
SIZE_T MethodDescChunk::SizeOfCompactEntryPoints(int count)
{
LIMITED_METHOD_DAC_CONTRACT;
#ifdef TARGET_ARM
return COMPACT_ENTRY_ARM_CODE + count * TEP_ENTRY_SIZE + TEP_CENTRAL_JUMP_SIZE;
#else // TARGET_ARM
int fullBlocks = count / TEP_MAX_BLOCK_INDEX;
int remainder = count % TEP_MAX_BLOCK_INDEX;
return 1 + (fullBlocks * TEP_FULL_BLOCK_SIZE) +
(remainder * TEP_ENTRY_SIZE) + ((remainder != 0) ? TEP_CENTRAL_JUMP_SIZE : 0);
#endif // TARGET_ARM
}
#ifndef DACCESS_COMPILE
TADDR MethodDescChunk::AllocateCompactEntryPoints(LoaderAllocator *pLoaderAllocator, AllocMemTracker *pamTracker)
{
CONTRACTL {
THROWS;
GC_NOTRIGGER;
} CONTRACTL_END;
int count = GetCount();
SIZE_T size = SizeOfCompactEntryPoints(count);
TADDR temporaryEntryPoints = (TADDR)pamTracker->Track(pLoaderAllocator->GetPrecodeHeap()->AllocAlignedMem(size, sizeof(TADDR)));
ExecutableWriterHolder<void> temporaryEntryPointsWriterHolder((void *)temporaryEntryPoints, size);
size_t rxOffset = temporaryEntryPoints - (TADDR)temporaryEntryPointsWriterHolder.GetRW();
#ifdef TARGET_ARM
BYTE* p = (BYTE*)temporaryEntryPointsWriterHolder.GetRW() + COMPACT_ENTRY_ARM_CODE;
int relOffset = count * TEP_ENTRY_SIZE - TEP_ENTRY_SIZE; // relative offset for the short jump
_ASSERTE (relOffset < MAX_OFFSET_UNCONDITIONAL_BRANCH_THUMB);
#else // TARGET_ARM
// make the temporary entrypoints unaligned, so they are easy to identify
BYTE* p = (BYTE*)temporaryEntryPointsWriterHolder.GetRW() + 1;
int indexInBlock = TEP_MAX_BLOCK_INDEX; // recompute relOffset in first iteration
int relOffset = 0; // relative offset for the short jump
#endif // TARGET_ARM
MethodDesc * pBaseMD = 0; // index of the start of the block
MethodDesc * pMD = GetFirstMethodDesc();
for (int index = 0; index < count; index++)
{
#ifdef TARGET_ARM
uint8_t *pMovInstrByte1 = (uint8_t *)p;
uint8_t *pMovInstrByte2 = (uint8_t *)p+1;
uint16_t *pBranchInstr = ((uint16_t *)p)+1;
*pMovInstrByte1 = TEP_ENTRY_INSTR1_BYTE1;
*pMovInstrByte2 = TEP_ENTRY_INSTR1_BYTE2;
*pBranchInstr = EncodeBranchToCentralJump ((int16_t) relOffset);
p += TEP_ENTRY_SIZE;
#else // TARGET_ARM
if (indexInBlock == TEP_MAX_BLOCK_INDEX)
{
relOffset = (min(count - index, TEP_MAX_BEFORE_INDEX) - 1) * TEP_ENTRY_SIZE;
indexInBlock = 0;
pBaseMD = pMD;
}
*(p+0) = X86_INSTR_MOV_AL;
int methodDescIndex = pMD->GetMethodDescChunkIndex() - pBaseMD->GetMethodDescChunkIndex();
_ASSERTE(FitsInU1(methodDescIndex));
*(p+1) = (BYTE)methodDescIndex;
*(p+2) = X86_INSTR_JMP_REL8;
_ASSERTE(FitsInI1(relOffset));
*(p+3) = (BYTE)relOffset;
p += TEP_ENTRY_SIZE; static_assert_no_msg(TEP_ENTRY_SIZE == 4);
if (relOffset == 0)
{
CentralJumpCode* pCode = (CentralJumpCode*)p;
CentralJumpCode* pCodeRX = (CentralJumpCode*)(p + rxOffset);
memcpy(pCode, &c_CentralJumpCode, TEP_CENTRAL_JUMP_SIZE);
pCode->Setup(pCodeRX, pBaseMD, GetPreStubEntryPoint(), pLoaderAllocator);
p += TEP_CENTRAL_JUMP_SIZE;
relOffset -= TEP_CENTRAL_JUMP_SIZE;
}
indexInBlock++;
#endif // TARGET_ARM
relOffset -= TEP_ENTRY_SIZE;
pMD = (MethodDesc *)((BYTE *)pMD + pMD->SizeOf());
}
#ifdef TARGET_ARM
CentralJumpCode* pCode = (CentralJumpCode*)p;
memcpy(pCode, &c_CentralJumpCode, TEP_CENTRAL_JUMP_SIZE);
pCode->Setup (GetPreStubCompactARMEntryPoint(), this);
_ASSERTE(p + TEP_CENTRAL_JUMP_SIZE == (BYTE*)temporaryEntryPointsWriterHolder.GetRW() + size);
#else // TARGET_ARM
_ASSERTE(p == (BYTE*)temporaryEntryPointsWriterHolder.GetRW() + size);
#endif // TARGET_ARM
ClrFlushInstructionCache((LPVOID)temporaryEntryPoints, size);
SetHasCompactEntryPoints();
return temporaryEntryPoints;
}
#endif // !DACCESS_COMPILE
#endif // HAS_COMPACT_ENTRYPOINTS
//*******************************************************************************
PCODE MethodDescChunk::GetTemporaryEntryPoint(int index)
{
LIMITED_METHOD_CONTRACT;
#ifdef HAS_COMPACT_ENTRYPOINTS
if (HasCompactEntryPoints())
{
#ifdef TARGET_ARM
return GetTemporaryEntryPoints() + COMPACT_ENTRY_ARM_CODE + THUMB_CODE + index * TEP_ENTRY_SIZE;
#else // TARGET_ARM
int fullBlocks = index / TEP_MAX_BLOCK_INDEX;
int remainder = index % TEP_MAX_BLOCK_INDEX;
return GetTemporaryEntryPoints() + 1 + (fullBlocks * TEP_FULL_BLOCK_SIZE) +
(remainder * TEP_ENTRY_SIZE) + ((remainder >= TEP_MAX_BEFORE_INDEX) ? TEP_CENTRAL_JUMP_SIZE : 0);
#endif // TARGET_ARM
}
#endif // HAS_COMPACT_ENTRYPOINTS
return Precode::GetPrecodeForTemporaryEntryPoint(GetTemporaryEntryPoints(), index)->GetEntryPoint();
}
PCODE MethodDesc::GetTemporaryEntryPoint()
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;
MethodDescChunk* pChunk = GetMethodDescChunk();
TADDR pEntryPoint = pChunk->GetTemporaryEntryPoint(GetMethodDescIndex());
#ifdef _DEBUG
MethodDesc * pMD = MethodDesc::GetMethodDescFromStubAddr(pEntryPoint);
_ASSERTE(PTR_HOST_TO_TADDR(this) == PTR_HOST_TO_TADDR(pMD));
#endif
return pEntryPoint;
}
#ifndef DACCESS_COMPILE
//*******************************************************************************
void MethodDesc::SetTemporaryEntryPoint(LoaderAllocator *pLoaderAllocator, AllocMemTracker *pamTracker)
{
WRAPPER_NO_CONTRACT;
GetMethodDescChunk()->EnsureTemporaryEntryPointsCreated(pLoaderAllocator, pamTracker);
PTR_PCODE pSlot = GetAddrOfSlot();
_ASSERTE(*pSlot == NULL);
*pSlot = GetTemporaryEntryPoint();
if (RequiresStableEntryPoint())
{
// The rest of the system assumes that certain methods always have stable entrypoints.
// Create them now.
GetOrCreatePrecode();
}
}
//*******************************************************************************
void MethodDescChunk::CreateTemporaryEntryPoints(LoaderAllocator *pLoaderAllocator, AllocMemTracker *pamTracker)
{
WRAPPER_NO_CONTRACT;
_ASSERTE(GetTemporaryEntryPoints() == NULL);
TADDR temporaryEntryPoints = Precode::AllocateTemporaryEntryPoints(this, pLoaderAllocator, pamTracker);
#ifdef HAS_COMPACT_ENTRYPOINTS
// Precodes allocated only if they provide more compact representation or if it is required
if (temporaryEntryPoints == NULL)
{
temporaryEntryPoints = AllocateCompactEntryPoints(pLoaderAllocator, pamTracker);
}
#endif // HAS_COMPACT_ENTRYPOINTS
*(((TADDR *)this)-1) = temporaryEntryPoints;
_ASSERTE(GetTemporaryEntryPoints() != NULL);
}
//*******************************************************************************
Precode* MethodDesc::GetOrCreatePrecode()
{
WRAPPER_NO_CONTRACT;
_ASSERTE(!IsVersionableWithVtableSlotBackpatch());
if (HasPrecode())
{
return GetPrecode();
}
PTR_PCODE pSlot = GetAddrOfSlot();
PCODE tempEntry = GetTemporaryEntryPoint();
PrecodeType requiredType = GetPrecodeType();
PrecodeType availableType = PRECODE_INVALID;
if (!GetMethodDescChunk()->HasCompactEntryPoints())
{
availableType = Precode::GetPrecodeFromEntryPoint(tempEntry)->GetType();
}
// Allocate the precode if necessary
if (requiredType != availableType)
{
// code:Precode::AllocateTemporaryEntryPoints should always create precode of the right type for dynamic methods.
// If we took this path for dynamic methods, the precode may leak since we may allocate it in domain-neutral loader heap.
_ASSERTE(!IsLCGMethod());
AllocMemTracker amt;
Precode* pPrecode = Precode::Allocate(requiredType, this, GetLoaderAllocator(), &amt);
if (InterlockedCompareExchangeT(pSlot, pPrecode->GetEntryPoint(), tempEntry) == tempEntry)
amt.SuppressRelease();
}
// Set the flags atomically
InterlockedUpdateFlags3(enum_flag3_HasStableEntryPoint | enum_flag3_HasPrecode, TRUE);
return Precode::GetPrecodeFromEntryPoint(*pSlot);
}
bool MethodDesc::DetermineIsEligibleForTieredCompilationInvariantForAllMethodsInChunk()
{
#ifdef FEATURE_TIERED_COMPILATION
#ifndef FEATURE_CODE_VERSIONING
#error Tiered compilation requires code versioning
#endif
return
// Policy
g_pConfig->TieredCompilation() &&
// Functional requirement
CodeVersionManager::IsMethodSupported(this) &&
// Policy - If QuickJit is disabled and the module does not have any pregenerated code, the method would effectively not
// be tiered currently, so make the method ineligible for tiering to avoid some unnecessary overhead
(g_pConfig->TieredCompilation_QuickJit() || GetMethodTable()->GetModule()->IsReadyToRun()) &&
// Policy - Tiered compilation is not disabled by the profiler
!CORProfilerDisableTieredCompilation() &&
// Policy - Generating optimized code is not disabled
!IsJitOptimizationDisabledForAllMethodsInChunk();
#else
return false;
#endif
}
bool MethodDesc::DetermineAndSetIsEligibleForTieredCompilation()
{
WRAPPER_NO_CONTRACT;
#ifdef FEATURE_TIERED_COMPILATION
#ifndef FEATURE_CODE_VERSIONING
#error Tiered compilation requires code versioning
#endif
// This function should only be called if the chunk has already been checked. This is done
// to reduce the amount of flags checked for each MethodDesc
_ASSERTE(DetermineIsEligibleForTieredCompilationInvariantForAllMethodsInChunk());
// Keep in-sync with MethodTableBuilder::NeedsNativeCodeSlot(bmtMDMethod * pMDMethod)
// to ensure native slots are available where needed.
if (
// Functional requirement - The NativeCodeSlot is required to hold the code pointer for the default code version because
// the method's entry point slot will point to a precode or to the current code entry point
HasNativeCodeSlot() &&
// Functional requirement - These methods have no IL that could be optimized
!IsWrapperStub() &&
// Functions with NoOptimization or AggressiveOptimization don't participate in tiering
!IsJitOptimizationLevelRequested())
{
m_wFlags3AndTokenRemainder |= enum_flag3_IsEligibleForTieredCompilation;
_ASSERTE(IsVersionable());
return true;
}
#endif
return false;
}
#endif // !DACCESS_COMPILE
bool MethodDesc::IsJitOptimizationDisabled()
{
WRAPPER_NO_CONTRACT;
return IsJitOptimizationDisabledForAllMethodsInChunk() ||
IsJitOptimizationDisabledForSpecificMethod();
}
bool MethodDesc::IsJitOptimizationDisabledForSpecificMethod()
{
return (!IsNoMetadata() && IsMiNoOptimization(GetImplAttrs()));
}
bool MethodDesc::IsJitOptimizationLevelRequested()
{
if (IsNoMetadata())
{
return false;
}
const DWORD attrs = GetImplAttrs();
return IsMiNoOptimization(attrs) || IsMiAggressiveOptimization(attrs);
}
bool MethodDesc::IsJitOptimizationDisabledForAllMethodsInChunk()
{
WRAPPER_NO_CONTRACT;
return
g_pConfig->JitMinOpts() ||
g_pConfig->GenDebuggableCode() ||
CORDisableJITOptimizations(GetModule()->GetDebuggerInfoBits());
}
#ifndef DACCESS_COMPILE
void MethodDesc::RecordAndBackpatchEntryPointSlot(
LoaderAllocator *slotLoaderAllocator, // the loader allocator from which the slot's memory is allocated
TADDR slot,
EntryPointSlots::SlotType slotType)
{
WRAPPER_NO_CONTRACT;
GCX_PREEMP();
LoaderAllocator *mdLoaderAllocator = GetLoaderAllocator();
MethodDescBackpatchInfoTracker::ConditionalLockHolder slotBackpatchLockHolder;
RecordAndBackpatchEntryPointSlot_Locked(
mdLoaderAllocator,
slotLoaderAllocator,
slot,
slotType,
GetEntryPointToBackpatch_Locked());
}
// This function tries to record a slot that would contain an entry point for the method, and backpatches the slot to contain
// method's current entry point. Once recorded, changes to the entry point due to tiering will cause the slot to be backpatched
// as necessary.
void MethodDesc::RecordAndBackpatchEntryPointSlot_Locked(
LoaderAllocator *mdLoaderAllocator,
LoaderAllocator *slotLoaderAllocator, // the loader allocator from which the slot's memory is allocated
TADDR slot,
EntryPointSlots::SlotType slotType,
PCODE currentEntryPoint)
{
WRAPPER_NO_CONTRACT;
_ASSERTE(MethodDescBackpatchInfoTracker::IsLockOwnedByCurrentThread());
_ASSERTE(mdLoaderAllocator != nullptr);
_ASSERTE(mdLoaderAllocator == GetLoaderAllocator());
_ASSERTE(slotLoaderAllocator != nullptr);
_ASSERTE(slot != NULL);
_ASSERTE(slotType < EntryPointSlots::SlotType_Count);
_ASSERTE(MayHaveEntryPointSlotsToBackpatch());
// The specified current entry point must actually be *current* in the sense that it must have been retrieved inside the
// lock, such that a recorded slot is guaranteed to point to the entry point at the time at which it was recorded, in order
// to synchronize with backpatching in MethodDesc::BackpatchEntryPointSlots(). If a slot pointing to an older entry point
// were to be recorded due to concurrency issues, it would not get backpatched to point to the more recent, actually
// current, entry point until another entry point change, which may never happen.
_ASSERTE(currentEntryPoint == GetEntryPointToBackpatch_Locked());
MethodDescBackpatchInfoTracker *backpatchTracker = mdLoaderAllocator->GetMethodDescBackpatchInfoTracker();
backpatchTracker->AddSlotAndPatch_Locked(this, slotLoaderAllocator, slot, slotType, currentEntryPoint);
}
FORCEINLINE bool MethodDesc::TryBackpatchEntryPointSlots(
PCODE entryPoint,
bool isPrestubEntryPoint,
bool onlyFromPrestubEntryPoint)
{
WRAPPER_NO_CONTRACT;
_ASSERTE(MayHaveEntryPointSlotsToBackpatch());
_ASSERTE(entryPoint != NULL);
_ASSERTE(isPrestubEntryPoint == (entryPoint == GetPrestubEntryPointToBackpatch()));
_ASSERTE(!isPrestubEntryPoint || !onlyFromPrestubEntryPoint);
_ASSERTE(MethodDescBackpatchInfoTracker::IsLockOwnedByCurrentThread());
LoaderAllocator *mdLoaderAllocator = GetLoaderAllocator();
MethodDescBackpatchInfoTracker *backpatchInfoTracker = mdLoaderAllocator->GetMethodDescBackpatchInfoTracker();
// Get the entry point to backpatch inside the lock to synchronize with backpatching in MethodDesc::DoBackpatch()
PCODE previousEntryPoint = GetEntryPointToBackpatch_Locked();
if (previousEntryPoint == entryPoint)
{
return true;
}
if (onlyFromPrestubEntryPoint && previousEntryPoint != GetPrestubEntryPointToBackpatch())
{
return false;
}
if (IsVersionableWithVtableSlotBackpatch())
{
// Backpatch the func ptr stub if it was created
FuncPtrStubs *funcPtrStubs = mdLoaderAllocator->GetFuncPtrStubsNoCreate();
if (funcPtrStubs != nullptr)
{
Precode *funcPtrPrecode = funcPtrStubs->Lookup(this);
if (funcPtrPrecode != nullptr)
{
if (isPrestubEntryPoint)
{
funcPtrPrecode->ResetTargetInterlocked();
}
else
{
funcPtrPrecode->SetTargetInterlocked(entryPoint, FALSE /* fOnlyRedirectFromPrestub */);
}
}
}
}
backpatchInfoTracker->Backpatch_Locked(this, entryPoint);
// Set the entry point to backpatch inside the lock to synchronize with backpatching in MethodDesc::DoBackpatch(), and set
// it last in case there are exceptions above, as setting the entry point indicates that all recorded slots have been
// backpatched
SetEntryPointToBackpatch_Locked(entryPoint);
return true;
}
void MethodDesc::TrySetInitialCodeEntryPointForVersionableMethod(
PCODE entryPoint,
bool mayHaveEntryPointSlotsToBackpatch)
{
WRAPPER_NO_CONTRACT;
_ASSERTE(entryPoint != NULL);
_ASSERTE(IsVersionable());
_ASSERTE(mayHaveEntryPointSlotsToBackpatch == MayHaveEntryPointSlotsToBackpatch());
if (mayHaveEntryPointSlotsToBackpatch)
{
TryBackpatchEntryPointSlotsFromPrestub(entryPoint);
}
else
{
_ASSERTE(IsVersionableWithPrecode());
GetOrCreatePrecode()->SetTargetInterlocked(entryPoint, TRUE /* fOnlyRedirectFromPrestub */);
}
}
void MethodDesc::SetCodeEntryPoint(PCODE entryPoint)
{
WRAPPER_NO_CONTRACT;
_ASSERTE(entryPoint != NULL);
if (MayHaveEntryPointSlotsToBackpatch())
{
BackpatchEntryPointSlots(entryPoint);
}
else if (IsVersionable())
{
_ASSERTE(IsVersionableWithPrecode());
GetOrCreatePrecode()->SetTargetInterlocked(entryPoint, FALSE /* fOnlyRedirectFromPrestub */);
// SetTargetInterlocked() would return false if it lost the race with another thread. That is fine, this thread
// can continue assuming it was successful, similarly to it successfully updating the target and another thread
// updating the target again shortly afterwards.
}
else if (HasPrecode())
{
GetPrecode()->SetTargetInterlocked(entryPoint);
}
else if (!HasStableEntryPoint())
{
SetStableEntryPointInterlocked(entryPoint);
}
}
void MethodDesc::ResetCodeEntryPoint()
{
WRAPPER_NO_CONTRACT;
_ASSERTE(IsVersionable());
if (MayHaveEntryPointSlotsToBackpatch())
{
BackpatchToResetEntryPointSlots();
return;
}
_ASSERTE(IsVersionableWithPrecode());
if (HasPrecode())
{
GetPrecode()->ResetTargetInterlocked();
}
}
void MethodDesc::ResetCodeEntryPointForEnC()
{
WRAPPER_NO_CONTRACT;
_ASSERTE(!IsVersionable());
_ASSERTE(!IsVersionableWithPrecode());
_ASSERTE(!MayHaveEntryPointSlotsToBackpatch());
LOG((LF_ENC, LL_INFO100000, "MD::RCEPFENC: this:%p - %s::%s - HasPrecode():%s, HasNativeCodeSlot():%s\n",
this, m_pszDebugClassName, m_pszDebugMethodName, (HasPrecode() ? "true" : "false"), (HasNativeCodeSlot() ? "true" : "false")));
if (HasPrecode())
{
GetPrecode()->ResetTargetInterlocked();
}
if (HasNativeCodeSlot())
{
PTR_PCODE ppCode = GetAddrOfNativeCodeSlot();
PCODE pCode = *ppCode;
LOG((LF_CORDB, LL_INFO1000000, "MD::RCEPFENC: %p -> %p\n",
ppCode, pCode));
*ppCode = NULL;
}
}
//*******************************************************************************
BOOL MethodDesc::SetNativeCodeInterlocked(PCODE addr, PCODE pExpected /*=NULL*/)
{
CONTRACTL {
THROWS;
GC_NOTRIGGER;
} CONTRACTL_END;
_ASSERTE(!IsDefaultInterfaceMethod() || HasNativeCodeSlot());
if (HasNativeCodeSlot())
{
#ifdef TARGET_ARM
_ASSERTE(IsThumbCode(addr) || (addr==NULL));
addr &= ~THUMB_CODE;
if (pExpected != NULL)
{
_ASSERTE(IsThumbCode(pExpected));
pExpected &= ~THUMB_CODE;
}
#endif
return InterlockedCompareExchangeT(GetAddrOfNativeCodeSlot(), addr, pExpected) == pExpected;
}
_ASSERTE(pExpected == NULL);
return SetStableEntryPointInterlocked(addr);
}
//*******************************************************************************
void MethodDesc::SetMethodEntryPoint(PCODE addr)
{
WRAPPER_NO_CONTRACT;
_ASSERTE(addr != NULL);
// Similarly to GetMethodEntryPoint(), it is up to the caller to ensure that calls to this function are appropriately
// synchronized. Currently, the only caller synchronizes with the following lock.
_ASSERTE(MethodDescBackpatchInfoTracker::IsLockOwnedByCurrentThread());
*GetAddrOfSlot() = addr;
}
//*******************************************************************************
BOOL MethodDesc::SetStableEntryPointInterlocked(PCODE addr)
{
CONTRACTL {
THROWS;
GC_NOTRIGGER;
} CONTRACTL_END;
_ASSERTE(!HasPrecode());
_ASSERTE(!IsVersionable());
PCODE pExpected = GetTemporaryEntryPoint();
PTR_PCODE pSlot = GetAddrOfSlot();
BOOL fResult = InterlockedCompareExchangeT(pSlot, addr, pExpected) == pExpected;
InterlockedUpdateFlags3(enum_flag3_HasStableEntryPoint, TRUE);
return fResult;
}
BOOL NDirectMethodDesc::ComputeMarshalingRequired()
{
WRAPPER_NO_CONTRACT;
return NDirect::MarshalingRequired(this);
}
/**********************************************************************************/
// Forward declare the NDirectImportWorker function - See dllimport.cpp
EXTERN_C LPVOID STDCALL NDirectImportWorker(NDirectMethodDesc*);
void *NDirectMethodDesc::ResolveAndSetNDirectTarget(_In_ NDirectMethodDesc* pMD)
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
INJECT_FAULT(COMPlusThrowOM(););
PRECONDITION(CheckPointer(pMD));
}
CONTRACTL_END
// This build conditional is here due to dllimport.cpp
// not being relevant during the crossgen build.
LPVOID targetMaybe = NDirectImportWorker(pMD);
_ASSERTE(targetMaybe != nullptr);
pMD->SetNDirectTarget(targetMaybe);
return targetMaybe;
}
BOOL NDirectMethodDesc::TryGetResolvedNDirectTarget(_In_ NDirectMethodDesc* pMD, _Out_ void** ndirectTarget)
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
INJECT_FAULT(COMPlusThrowOM(););
PRECONDITION(CheckPointer(pMD));
PRECONDITION(CheckPointer(ndirectTarget));
}
CONTRACTL_END
if (!pMD->NDirectTargetIsImportThunk())
{
// This is an early out to handle already resolved targets
*ndirectTarget = pMD->GetNDirectTarget();
return TRUE;
}
if (!pMD->ShouldSuppressGCTransition())
return FALSE;
*ndirectTarget = ResolveAndSetNDirectTarget(pMD);
return TRUE;
}
//*******************************************************************************
void NDirectMethodDesc::InterlockedSetNDirectFlags(WORD wFlags)
{
CONTRACTL
{
THROWS;
GC_NOTRIGGER;
}
CONTRACTL_END
// Since InterlockedCompareExchange only works on ULONGs,
// we'll have to operate on the entire ULONG. Ugh.
WORD *pFlags = &ndirect.m_wFlags;
// Make sure that m_flags is aligned on a 4 byte boundry
_ASSERTE( ( ((size_t) pFlags) & (sizeof(ULONG)-1) ) == 0);
// Ensure we won't be reading or writing outside the bounds of the NDirectMethodDesc.
_ASSERTE((BYTE*)pFlags >= (BYTE*)this);
_ASSERTE((BYTE*)pFlags+sizeof(ULONG) <= (BYTE*)(this+1));
DWORD dwMask = 0;
// Set the flags in the mask
((WORD*)&dwMask)[0] |= wFlags;
// Now, slam all 32 bits atomically.
InterlockedOr((LONG*)pFlags, dwMask);
}
#ifdef TARGET_WINDOWS
FARPROC NDirectMethodDesc::FindEntryPointWithMangling(NATIVE_LIBRARY_HANDLE hMod, PTR_CUTF8 entryPointName)
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
}
CONTRACTL_END;
FARPROC pFunc = GetProcAddress(hMod, entryPointName);
#if defined(TARGET_X86)
if (pFunc)
{
return pFunc;
}
if (IsStdCall())
{
EnsureStackArgumentSize();
DWORD probedEntrypointNameLength = (DWORD)(strlen(entryPointName) + 1); // 1 for null terminator
int dstbufsize = (int)(sizeof(char) * (probedEntrypointNameLength + 10)); // 10 for stdcall mangling
LPSTR szProbedEntrypointName = ((LPSTR)_alloca(dstbufsize + 1));
szProbedEntrypointName[0] = '_';
strcpy_s(szProbedEntrypointName + 1, dstbufsize, entryPointName);
szProbedEntrypointName[probedEntrypointNameLength] = '\0'; // Add an extra '\0'.
UINT16 numParamBytesMangle = GetStackArgumentSize();
sprintf_s(szProbedEntrypointName + probedEntrypointNameLength, dstbufsize - probedEntrypointNameLength + 1, "@%lu", (ULONG)numParamBytesMangle);
pFunc = GetProcAddress(hMod, szProbedEntrypointName);
}
#endif
return pFunc;
}
FARPROC NDirectMethodDesc::FindEntryPointWithSuffix(NATIVE_LIBRARY_HANDLE hMod, PTR_CUTF8 entryPointName, char suffix)
{
// Allocate space for a copy of the entry point name.
DWORD entryPointWithSuffixLen = (DWORD)(strlen(entryPointName) + 1); // +1 for charset decorations
int dstbufsize = (int)(sizeof(char) * (entryPointWithSuffixLen + 1)); // +1 for the null terminator
LPSTR entryPointWithSuffix = ((LPSTR)_alloca(dstbufsize));
// Copy the name so we can mangle it.
strcpy_s(entryPointWithSuffix, dstbufsize, entryPointName);
entryPointWithSuffix[entryPointWithSuffixLen] = '\0'; // Null terminator
entryPointWithSuffix[entryPointWithSuffixLen - 1] = suffix; // Charset suffix
// Look for entry point with the suffix based on charset
return FindEntryPointWithMangling(hMod, entryPointWithSuffix);
}
#endif
//*******************************************************************************
LPVOID NDirectMethodDesc::FindEntryPoint(NATIVE_LIBRARY_HANDLE hMod)
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
}
CONTRACTL_END;
char const * funcName = GetEntrypointName();
#ifndef TARGET_WINDOWS
return reinterpret_cast<LPVOID>(PAL_GetProcAddressDirect(hMod, funcName));
#else
// Handle ordinals.
if (funcName[0] == '#')
{
long ordinal = atol(funcName + 1);
return reinterpret_cast<LPVOID>(GetProcAddress(hMod, (LPCSTR)(size_t)((UINT16)ordinal)));
}
FARPROC pFunc = NULL;
if (IsNativeNoMangled())
{
// Look for the user-provided entry point name only
pFunc = FindEntryPointWithMangling(hMod, funcName);
}
else if (IsNativeAnsi())
{
// For ANSI, look for the user-provided entry point name first.
// If that does not exist, try the charset suffix.
pFunc = FindEntryPointWithMangling(hMod, funcName);
if (pFunc == NULL)
pFunc = FindEntryPointWithSuffix(hMod, funcName, 'A');
}
else
{
// For Unicode, look for the entry point name with the charset suffix first.
// The 'W' API takes precedence over the undecorated one.
pFunc = FindEntryPointWithSuffix(hMod, funcName, 'W');
if (pFunc == NULL)
pFunc = FindEntryPointWithMangling(hMod, funcName);
}
return reinterpret_cast<LPVOID>(pFunc);
#endif
}
#if defined(TARGET_X86)
//*******************************************************************************
void NDirectMethodDesc::EnsureStackArgumentSize()
{
STANDARD_VM_CONTRACT;
if (ndirect.m_cbStackArgumentSize == 0xFFFF)
{
// Marshalling required check sets the stack size as side-effect when marshalling is not required.
if (MarshalingRequired())
{
// Generating interop stub sets the stack size as side-effect in all cases
GetStubForInteropMethod(this, NDIRECTSTUB_FL_FOR_NUMPARAMBYTES);
}
}
}
#endif
//*******************************************************************************
void NDirectMethodDesc::InitEarlyBoundNDirectTarget()
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
INJECT_FAULT(COMPlusThrowOM(););
}
CONTRACTL_END
_ASSERTE(IsEarlyBound());
if (IsClassConstructorTriggeredAtLinkTime())
{
GetMethodTable()->CheckRunClassInitThrowing();
}
const void *target = GetModule()->GetInternalPInvokeTarget(GetRVA());
_ASSERTE(target != 0);
if (HeuristicDoesThisLookLikeAGetLastErrorCall((LPBYTE)target))
target = (BYTE*)FalseGetLastError;
// As long as we've set the NDirect target field we don't need to backpatch the import thunk glue.
// All NDirect calls all through the NDirect target, so if it's updated, then we won't go into
// NDirectImportThunk(). In fact, backpatching the import thunk glue leads to race conditions.
SetNDirectTarget((LPVOID)target);
}
//*******************************************************************************
BOOL MethodDesc::HasUnmanagedCallersOnlyAttribute()
{
CONTRACTL
{
THROWS;
GC_NOTRIGGER;
FORBID_FAULT;
}
CONTRACTL_END;
if (IsILStub())
{
// Stubs generated for being called from native code are equivalent to
// managed methods marked with UnmanagedCallersOnly.
return AsDynamicMethodDesc()->GetILStubType() == DynamicMethodDesc::StubNativeToCLRInterop;
}
HRESULT hr = GetCustomAttribute(
WellKnownAttribute::UnmanagedCallersOnly,
nullptr,
nullptr);
if (hr != S_OK)
{
// See https://github.com/dotnet/runtime/issues/37622
hr = GetCustomAttribute(
WellKnownAttribute::NativeCallableInternal,
nullptr,
nullptr);
}
return (hr == S_OK) ? TRUE : FALSE;
}
//*******************************************************************************
BOOL MethodDesc::ShouldSuppressGCTransition()
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
INJECT_FAULT(COMPlusThrowOM());
}
CONTRACTL_END;
MethodDesc* tgt = nullptr;
if (IsNDirect())
{
tgt = this;
}
else if (IsILStub())
{
// From the IL stub, determine if the actual target has been
// marked to suppress the GC transition.
PTR_DynamicMethodDesc ilStubMD = AsDynamicMethodDesc();
PTR_ILStubResolver ilStubResolver = ilStubMD->GetILStubResolver();
tgt = ilStubResolver->GetStubTargetMethodDesc();
// In the event we can't get or don't have a target, there is no way
// to determine if we should suppress the GC transition.
if (tgt == nullptr)
return FALSE;
}
else
{
return FALSE;
}
_ASSERTE(tgt != nullptr);
bool suppressGCTransition;
NDirect::GetCallingConvention_IgnoreErrors(tgt, NULL /*callConv*/, &suppressGCTransition);
return suppressGCTransition ? TRUE : FALSE;
}
#ifdef FEATURE_COMINTEROP
//*******************************************************************************
void ComPlusCallMethodDesc::InitComEventCallInfo()
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
INJECT_FAULT(COMPlusThrowOM());
}
CONTRACTL_END
MethodTable *pItfMT = GetInterfaceMethodTable();
MethodDesc *pItfMD = this;
MethodTable *pSrcItfClass = NULL;
MethodTable *pEvProvClass = NULL;
// Retrieve the event provider class.
WORD cbExtraSlots = ComMethodTable::GetNumExtraSlots(pItfMT->GetComInterfaceType());
WORD itfSlotNum = (WORD) m_pComPlusCallInfo->m_cachedComSlot - cbExtraSlots;
pItfMT->GetEventInterfaceInfo(&pSrcItfClass, &pEvProvClass);
m_pComPlusCallInfo->m_pEventProviderMD = MemberLoader::FindMethodForInterfaceSlot(pEvProvClass, pItfMT, itfSlotNum);
// If we could not find the method, then the event provider does not support
// this event. This is a fatal error.
if (!m_pComPlusCallInfo->m_pEventProviderMD)
{
// Init the interface MD for error reporting.
pItfMD = (ComPlusCallMethodDesc*)pItfMT->GetMethodDescForSlot(itfSlotNum);
// Retrieve the event provider class name.
StackSString ssEvProvClassName;
pEvProvClass->_GetFullyQualifiedNameForClass(ssEvProvClassName);
// Retrieve the COM event interface class name.
StackSString ssEvItfName;
pItfMT->_GetFullyQualifiedNameForClass(ssEvItfName);
// Convert the method name to unicode.
StackSString ssMethodName(SString::Utf8, pItfMD->GetName());
// Throw the exception.
COMPlusThrow(kTypeLoadException, IDS_EE_METHOD_NOT_FOUND_ON_EV_PROV,
ssMethodName.GetUnicode(), ssEvItfName.GetUnicode(), ssEvProvClassName.GetUnicode());
}
}
#endif // FEATURE_COMINTEROP
#endif // !DACCESS_COMPILE
#ifdef DACCESS_COMPILE
//*******************************************************************************
void
MethodDesc::EnumMemoryRegions(CLRDataEnumMemoryFlags flags)
{
SUPPORTS_DAC;
if (DacHasMethodDescBeenEnumerated(this))
{
return;
}
// Save away the whole MethodDescChunk as in many
// places RecoverChunk is called on a method desc so
// the whole chunk must be available. This also
// automatically picks up any prestubs and such.
GetMethodDescChunk()->EnumMemoryRegions(flags);
if (HasPrecode())
{
GetPrecode()->EnumMemoryRegions(flags);
}
// Need to save the Debug-Info for this method so that we can see it in a debugger later.
DebugInfoManager::EnumMemoryRegionsForMethodDebugInfo(flags, this);
if (!IsNoMetadata() ||IsILStub())
{
// The assembling of the string below implicitly dumps the memory we need.
StackSString str;
TypeString::AppendMethodInternal(str, this, TypeString::FormatSignature|TypeString::FormatNamespace|TypeString::FormatFullInst);
#ifdef FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
if (flags == CLRDATA_ENUM_MEM_MINI || flags == CLRDATA_ENUM_MEM_TRIAGE)
{
// we want to save just the method name, so truncate at the open paranthesis
SString::Iterator it = str.Begin();
if (str.Find(it, W('(')))
{
// ensure the symbol ends in "()" to minimize regressions
// in !analyze assuming the existence of the argument list
str.Truncate(++it);
str.Append(W(')'));
}
DacMdCacheAddEEName(dac_cast<TADDR>(this), str);
}
#endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
// The module path is used in the output of !clrstack and !pe if the
// module is not available when the minidump is inspected. By retrieving
// the path here, the required memory is implicitly dumped.
Module* pModule = GetModule();
if (pModule)
{
pModule->GetPath();
}
}
#ifdef FEATURE_CODE_VERSIONING
// Make sure the active IL and native code version are in triage dumps.
CodeVersionManager* pCodeVersionManager = GetCodeVersionManager();
ILCodeVersion ilVersion = pCodeVersionManager->GetActiveILCodeVersion(dac_cast<PTR_MethodDesc>(this));
if (!ilVersion.IsNull())
{
ilVersion.GetActiveNativeCodeVersion(dac_cast<PTR_MethodDesc>(this));
ilVersion.GetVersionId();
ilVersion.GetRejitState();
ilVersion.GetIL();
}
#endif
// Also, call DacValidateMD to dump the memory it needs. !clrstack calls
// DacValidateMD before it retrieves the method name. We don't expect
// DacValidateMD to fail, but if it does, ignore the failure and try to assemble the
// string anyway so that clients that don't validate the MD still work.
DacValidateMD(this);
DacSetMethodDescEnumerated(this);
}
//*******************************************************************************
void
StoredSigMethodDesc::EnumMemoryRegions(CLRDataEnumMemoryFlags flags)
{
SUPPORTS_DAC;
// 'this' already done, see below.
DacEnumMemoryRegion(GetSigRVA(), m_cSig);
}
//*******************************************************************************
void
MethodDescChunk::EnumMemoryRegions(CLRDataEnumMemoryFlags flags)
{
SUPPORTS_DAC;
DAC_CHECK_ENUM_THIS();
EMEM_OUT(("MEM: %p MethodDescChunk\n", dac_cast<TADDR>(this)));
DacEnumMemoryRegion(dac_cast<TADDR>(this), SizeOf());
PTR_MethodTable pMT = GetMethodTable();
if (pMT.IsValid())
{
pMT->EnumMemoryRegions(flags);
}
SIZE_T size;
#ifdef HAS_COMPACT_ENTRYPOINTS
if (HasCompactEntryPoints())
{
size = SizeOfCompactEntryPoints(GetCount());
}
else
#endif // HAS_COMPACT_ENTRYPOINTS
{
size = Precode::SizeOfTemporaryEntryPoints(GetTemporaryEntryPoints(), GetCount());
}
DacEnumMemoryRegion(GetTemporaryEntryPoints(), size);
MethodDesc * pMD = GetFirstMethodDesc();
MethodDesc * pOldMD = NULL;
while (pMD != NULL && pMD != pOldMD)
{
pOldMD = pMD;
EX_TRY
{
if (pMD->IsMethodImpl())
{
pMD->GetMethodImpl()->EnumMemoryRegions(flags);
}
}
EX_CATCH_RETHROW_ONLY_COR_E_OPERATIONCANCELLED
EX_TRY
{
if (pMD->HasStoredSig())
{
dac_cast<PTR_StoredSigMethodDesc>(pMD)->EnumMemoryRegions(flags);
}
// Check whether the next MethodDesc is within the bounds of the current chunks
TADDR pNext = dac_cast<TADDR>(pMD) + pMD->SizeOf();
TADDR pEnd = dac_cast<TADDR>(this) + this->SizeOf();
pMD = (pNext < pEnd) ? PTR_MethodDesc(pNext) : NULL;
}
EX_CATCH_RETHROW_ONLY_COR_E_OPERATIONCANCELLED
}
}
#endif // DACCESS_COMPILE
#ifndef DACCESS_COMPILE
//*******************************************************************************
MethodDesc *MethodDesc::GetInterfaceMD()
{
CONTRACT (MethodDesc*) {
THROWS;
GC_TRIGGERS;
INSTANCE_CHECK;
PRECONDITION(!IsInterface());
POSTCONDITION(CheckPointer(RETVAL, NULL_OK));
} CONTRACT_END;
MethodTable *pMT = GetMethodTable();
RETURN(pMT->ReverseInterfaceMDLookup(GetSlot()));
}
#endif // !DACCESS_COMPILE
PTR_LoaderAllocator MethodDesc::GetLoaderAllocator()
{
WRAPPER_NO_CONTRACT;
return GetLoaderModule()->GetLoaderAllocator();
}
#if !defined(DACCESS_COMPILE)
REFLECTMETHODREF MethodDesc::GetStubMethodInfo()
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
INJECT_FAULT(COMPlusThrowOM());
MODE_COOPERATIVE;
}
CONTRACTL_END;
REFLECTMETHODREF retVal;
REFLECTMETHODREF methodRef = (REFLECTMETHODREF)AllocateObject(CoreLibBinder::GetClass(CLASS__STUBMETHODINFO));
GCPROTECT_BEGIN(methodRef);
methodRef->SetMethod(this);
LoaderAllocator *pLoaderAllocatorOfMethod = this->GetLoaderAllocator();
if (pLoaderAllocatorOfMethod->IsCollectible())
methodRef->SetKeepAlive(pLoaderAllocatorOfMethod->GetExposedObject());
retVal = methodRef;
GCPROTECT_END();
return retVal;
}
#endif // !DACCESS_COMPILE
#ifndef DACCESS_COMPILE
typedef void (*WalkValueTypeParameterFnPtr)(Module *pModule, mdToken token, Module *pDefModule, mdToken tkDefToken, SigPointer *ptr, SigTypeContext *pTypeContext, void *pData);
void MethodDesc::WalkValueTypeParameters(MethodTable *pMT, WalkValueTypeParameterFnPtr function, void *pData)
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
}
CONTRACTL_END;
uint32_t numArgs = 0;
Module *pModule = this->GetModule();
SigPointer ptr = this->GetSigPointer();
// skip over calling convention.
uint32_t callConv = 0;
IfFailThrowBF(ptr.GetCallingConvInfo(&callConv), BFA_BAD_SIGNATURE, pModule);
// If calling convention is generic, skip GenParamCount
if (callConv & IMAGE_CEE_CS_CALLCONV_GENERIC)
{
IfFailThrowBF(ptr.GetData(NULL), BFA_BAD_SIGNATURE, pModule);
}
IfFailThrowBF(ptr.GetData(&numArgs), BFA_BAD_SIGNATURE, pModule);
SigTypeContext typeContext(this, TypeHandle(pMT));
// iterate over the return type and parameters
for (DWORD j = 0; j <= numArgs; j++)
{
CorElementType type = ptr.PeekElemTypeClosed(pModule, &typeContext);
if (type != ELEMENT_TYPE_VALUETYPE)
goto moveToNextToken;
mdToken token;
Module *pTokenModule;
token = ptr.PeekValueTypeTokenClosed(pModule, &typeContext, &pTokenModule);
if (token == mdTokenNil)
goto moveToNextToken;
DWORD dwAttrType;
Module *pDefModule;
mdToken defToken;
dwAttrType = 0;
if (ClassLoader::ResolveTokenToTypeDefThrowing(pTokenModule, token, &pDefModule, &defToken))
{
if (function != NULL)
function(pModule, token, pDefModule, defToken, &ptr, &typeContext, pData);
}
moveToNextToken:
// move to next argument token
IfFailThrowBF(ptr.SkipExactlyOne(), BFA_BAD_SIGNATURE, pModule);
}
if (!HaveValueTypeParametersBeenWalked())
{
SetValueTypeParametersWalked();
}
}
PrecodeType MethodDesc::GetPrecodeType()
{
LIMITED_METHOD_CONTRACT;
PrecodeType precodeType = PRECODE_INVALID;
#ifdef HAS_FIXUP_PRECODE
if (!RequiresMethodDescCallingConvention())
{
// Use the more efficient fixup precode if possible
precodeType = PRECODE_FIXUP;
}
else
#endif // HAS_FIXUP_PRECODE
{
precodeType = PRECODE_STUB;
}
return precodeType;
}
#endif // !DACCESS_COMPILE
#ifdef FEATURE_COMINTEROP
#ifndef DACCESS_COMPILE
void ComPlusCallMethodDesc::InitRetThunk()
{
WRAPPER_NO_CONTRACT;
#ifdef TARGET_X86
if (m_pComPlusCallInfo->m_pRetThunk != NULL)
return;
UINT numStackBytes = CbStackPop();
LPVOID pRetThunk = ComPlusCall::GetRetThunk(numStackBytes);
InterlockedCompareExchangeT<void *>(&m_pComPlusCallInfo->m_pRetThunk, pRetThunk, NULL);
#endif // TARGET_X86
}
#endif //!DACCESS_COMPILE
#endif // FEATURE_COMINTEROP
#ifndef DACCESS_COMPILE
void MethodDesc::PrepareForUseAsADependencyOfANativeImageWorker()
{
STANDARD_VM_CONTRACT;
// This function ensures that a method is ready for use as a dependency of a native image
// The current requirement is only that valuetypes can be resolved to their type defs as much
// as is possible. (If the method is actually called, then this will not throw, but there
// are cases where we call this method and we are unaware if this method will actually be called
// or accessed as a native image dependency. This explains the contract (STANDARD_VM_CONTRACT)
// - This method should be callable only when general purpose VM code can be called
// , as well as the TRY/CATCH.
// - This function should not introduce failures
EX_TRY
{
WalkValueTypeParameters(this->GetMethodTable(), NULL, NULL);
}
EX_CATCH
{
}
EX_END_CATCH(RethrowTerminalExceptions);
_ASSERTE(HaveValueTypeParametersBeenWalked());
}
static void CheckForEquivalenceAndLoadType(Module *pModule, mdToken token, Module *pDefModule, mdToken defToken, const SigParser *ptr, SigTypeContext *pTypeContext, void *pData)
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
}
CONTRACTL_END;
BOOL *pHasEquivalentParam = (BOOL *)pData;
#ifdef FEATURE_TYPEEQUIVALENCE
*pHasEquivalentParam = IsTypeDefEquivalent(defToken, pDefModule);
#else
_ASSERTE(*pHasEquivalentParam == FALSE); // Assert this is always false.
#endif // FEATURE_TYPEEQUIVALENCE
SigPointer sigPtr(*ptr);
TypeHandle th = sigPtr.GetTypeHandleThrowing(pModule, pTypeContext);
_ASSERTE(!th.IsNull());
}
void MethodDesc::PrepareForUseAsAFunctionPointer()
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
}
CONTRACTL_END;
// Since function pointers are unsafe and can enable type punning, all
// value type parameters must be loaded prior to providing a function pointer.
if (HaveValueTypeParametersBeenLoaded())
return;
BOOL fHasTypeEquivalentStructParameters = FALSE;
WalkValueTypeParameters(this->GetMethodTable(), CheckForEquivalenceAndLoadType, &fHasTypeEquivalentStructParameters);
#ifdef FEATURE_TYPEEQUIVALENCE
if (!fHasTypeEquivalentStructParameters)
SetDoesNotHaveEquivalentValuetypeParameters();
#endif // FEATURE_TYPEEQUIVALENCE
SetValueTypeParametersLoaded();
}
#endif //!DACCESS_COMPILE