Files
Elinor Fung 36287c22bc Remove Unrestored/UnrestoredTypeKey flags, load levels, checks (#97674)
These were from NGEN support, so we can remove the flags, load levels, and assorted checks for them.
2024-01-31 14:52:50 -08:00

1675 lines
69 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: genmeth.cpp
//
// Most functionality for generic methods is put here
//
#include "common.h"
#include "method.hpp"
#include "field.h"
#include "eeconfig.h"
#include "crst.h"
#include "generics.h"
#include "genericdict.h"
#include "instmethhash.h"
#include "typestring.h"
#include "typedesc.h"
#include "comdelegate.h"
// Instantiated generic methods
//
// Method descriptors for instantiated generic methods are allocated on demand and inserted
// into the InstMethodHashTable for the LoaderModule of the descriptor. (See ceeload.h for more
// information about loader modules).
//
// For non-shared instantiations, entering the prestub for such a method descriptor causes the method to
// be JIT-compiled, specialized to that instantiation.
//
// For shared instantiations, entering the prestub generates a piece of stub code that passes the
// method descriptor as an extra argument and then jumps to code shared between compatible
// instantiations. This code has its own method descriptor whose instantiation is *canonical*
// (with reference-type type parameters replaced by Object).
//
// Thus for example the shared method descriptor for m<object> is different to the
// exact-instantiation method descriptor for m<object>.
//
// Complete example:
//
// class C<T> { public void m<S>(S x, T y) { ... } }
//
// Suppose that code sharing is turned on.
//
// Upon compiling calls to C<string>.m<string>, C<string>.m<Type>, C<Type>.m<string> and C<Type>.m<Type>
// Given a generic method descriptor and an instantiation, create a new instantiated method
// descriptor and chain it into the list attached to the generic method descriptor
//
// pMT is the owner method table. If looking for a shared MD this should be
// the MT for the shared class.
//
// pGenericMD is the generic method descriptor (owner may be instantiated)
// pWrappedMD is the corresponding shared md for use when creating stubs
// nGenericMethodArgs/genericMethodArgs is the instantiation
// getWrappedCode=TRUE if you want a shared instantiated md whose code expects an extra argument. In this
// case pWrappedMD should be NULL.
//
// The result is put in ppMD
//
// If getWrappedCode. In thise case the genericMethodArgs
// should be the normalized representative genericMethodArgs (see typehandle.h)
//
// Helper method that creates a method-desc off a template method desc
static MethodDesc* CreateMethodDesc(LoaderAllocator *pAllocator,
MethodTable *pMT,
MethodDesc *pTemplateMD,
DWORD classification,
BOOL fNativeCodeSlot,
AllocMemTracker *pamTracker)
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
INJECT_FAULT(COMPlusThrowOM(););
PRECONDITION(CheckPointer(pAllocator));
PRECONDITION(CheckPointer(pMT));
PRECONDITION(CheckPointer(pTemplateMD));
PRECONDITION(pTemplateMD->GetMethodTable()->GetCanonicalMethodTable() == pMT->GetCanonicalMethodTable());
}
CONTRACTL_END
mdMethodDef token = pTemplateMD->GetMemberDef();
// Create a singleton chunk for the method desc
MethodDescChunk *pChunk =
MethodDescChunk::CreateChunk(pAllocator->GetHighFrequencyHeap(),
1,
classification,
TRUE /* fNonVtableSlot*/,
fNativeCodeSlot,
pMT,
pamTracker);
// Now initialize the MDesc at the single method descriptor in
// the new chunk
MethodDesc *pMD = pChunk->GetFirstMethodDesc();
//We copy over the flags one by one. This is fragile w.r.t. adding
// new flags, but other techniques are also fragile. <NICE>We should move
// to using constructors on MethodDesc</NICE>
if (pTemplateMD->IsStatic())
{
pMD->SetStatic();
}
if (pTemplateMD->IsNotInline())
{
pMD->SetNotInline(true);
}
if (pTemplateMD->IsSynchronized())
{
pMD->SetSynchronized();
}
if (pTemplateMD->IsIntrinsic())
{
pMD->SetIsIntrinsic();
}
#ifdef FEATURE_METADATA_UPDATER
if (pTemplateMD->IsEnCAddedMethod())
{
pMD->SetIsEnCAddedMethod();
}
#endif // FEATURE_METADATA_UPDATER
pMD->SetMemberDef(token);
pMD->SetSlot(pTemplateMD->GetSlot());
#ifdef _DEBUG
//<NICE> more info here</NICE>
pMD->m_pszDebugMethodSignature = "<generic method signature>";
pMD->m_pszDebugClassName = "<generic method class name>";
pMD->m_pszDebugMethodName = "<generic method name>";
pMD->m_pDebugMethodTable = pMT;
#endif // _DEBUG
return pMD;
}
//
// The following methods map between tightly bound boxing and unboxing MethodDesc.
// We always layout boxing and unboxing MethodDescs next to each other in same
// MethodDescChunk. It allows us to avoid brute-force iteration over all methods
// on the type to perform the mapping.
//
//
// Find matching tightly-bound methoddesc
//
static MethodDesc * FindTightlyBoundWrappedMethodDesc(MethodDesc * pMD)
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
PRECONDITION(CheckPointer(pMD));
}
CONTRACTL_END
if (pMD->IsUnboxingStub() && pMD->GetClassification() == mcInstantiated)
pMD = pMD->AsInstantiatedMethodDesc()->IMD_GetWrappedMethodDesc();
// Find matching MethodDesc in the MethodTable
if (!pMD->IsTightlyBoundToMethodTable())
pMD = pMD->GetCanonicalMethodTable()->GetParallelMethodDesc(pMD);
_ASSERTE(pMD->IsTightlyBoundToMethodTable());
// Real MethodDesc immediately follows unboxing stub
if (pMD->IsUnboxingStub())
pMD = MethodTable::IntroducedMethodIterator::GetNext(pMD);
_ASSERTE(!pMD->IsUnboxingStub());
return pMD;
}
//
// Find matching tightly-bound unboxing stub if there is one
//
static MethodDesc * FindTightlyBoundUnboxingStub(MethodDesc * pMD)
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
PRECONDITION(CheckPointer(pMD));
}
CONTRACTL_END
// Find matching MethodDesc in the MethodTable
if (!pMD->IsTightlyBoundToMethodTable())
pMD = pMD->GetCanonicalMethodTable()->GetParallelMethodDesc(pMD);
_ASSERTE(pMD->IsTightlyBoundToMethodTable());
// We are done if we have unboxing stub already
if (pMD->IsUnboxingStub())
return pMD;
//
// Unboxing stub immediately precedes real methoddesc
//
MethodDesc * pCurMD = pMD->GetMethodDescChunk()->GetFirstMethodDesc();
if (pCurMD == pMD)
return NULL;
for (;;)
{
MethodDesc * pNextMD = MethodTable::IntroducedMethodIterator::GetNext(pCurMD);
if (pNextMD == pMD)
break;
pCurMD = pNextMD;
}
return pCurMD->IsUnboxingStub() ? pCurMD : NULL;
}
#ifdef _DEBUG
//
// Alternative brute-force implementation of FindTightlyBoundWrappedMethodDesc for debug-only check.
//
// Please note that this does not do the same up-front checks as the non-debug version to
// see whether or not the input pMD is even an unboxing stub in the first place.
//
static MethodDesc * FindTightlyBoundWrappedMethodDesc_DEBUG(MethodDesc * pMD)
{
CONTRACTL
{
THROWS;
GC_NOTRIGGER;
PRECONDITION(CheckPointer(pMD));
}
CONTRACTL_END
mdMethodDef methodDef = pMD->GetMemberDef();
Module *pModule = pMD->GetModule();
MethodTable::MethodIterator it(pMD->GetCanonicalMethodTable());
it.MoveToEnd();
for (; it.IsValid(); it.Prev()) {
if (!it.IsVirtual()) {
// Get the MethodDesc for current method
MethodDesc* pCurMethod = it.GetMethodDesc();
if (pCurMethod && !pCurMethod->IsUnboxingStub()) {
if ((pCurMethod->GetMemberDef() == methodDef) &&
(pCurMethod->GetModule() == pModule))
{
return pCurMethod;
}
}
}
}
return NULL;
}
//
// Alternative brute-force implementation of FindTightlyBoundUnboxingStub for debug-only check
//
// Please note that this does not do the same up-front checks as the non-debug version to
// see whether or not the input pMD even qualifies to have a corresponding unboxing stub.
//
static MethodDesc * FindTightlyBoundUnboxingStub_DEBUG(MethodDesc * pMD)
{
CONTRACTL
{
THROWS;
GC_NOTRIGGER;
PRECONDITION(CheckPointer(pMD));
}
CONTRACTL_END
mdMethodDef methodDef = pMD->GetMemberDef();
Module *pModule = pMD->GetModule();
MethodTable::MethodIterator it(pMD->GetCanonicalMethodTable());
it.MoveToEnd();
for (; it.IsValid(); it.Prev()) {
if (it.IsVirtual()) {
MethodDesc* pCurMethod = it.GetMethodDesc();
if (pCurMethod && pCurMethod->IsUnboxingStub()) {
if ((pCurMethod->GetMemberDef() == methodDef) &&
(pCurMethod->GetModule() == pModule)) {
return pCurMethod;
}
}
}
}
return NULL;
}
#endif // _DEBUG
/* static */
InstantiatedMethodDesc *
InstantiatedMethodDesc::NewInstantiatedMethodDesc(MethodTable *pExactMT,
MethodDesc* pGenericMDescInRepMT,
MethodDesc* pWrappedMD,
Instantiation methodInst,
BOOL getWrappedCode)
{
CONTRACT(InstantiatedMethodDesc*)
{
THROWS;
GC_TRIGGERS;
INJECT_FAULT(COMPlusThrowOM(););
PRECONDITION(CheckPointer(pExactMT));
PRECONDITION(CheckPointer(pGenericMDescInRepMT));
PRECONDITION(methodInst.IsEmpty() || pGenericMDescInRepMT->IsGenericMethodDefinition());
PRECONDITION(methodInst.GetNumArgs() == pGenericMDescInRepMT->GetNumGenericMethodArgs());
POSTCONDITION(CheckPointer(RETVAL));
POSTCONDITION(getWrappedCode == RETVAL->IsSharedByGenericInstantiations());
POSTCONDITION(methodInst.IsEmpty() || RETVAL->HasMethodInstantiation());
}
CONTRACT_END;
// All instantiated method descs live off the RepMT for the
// instantiated class they live in.
INDEBUG(MethodTable * pCanonMT = pExactMT->GetCanonicalMethodTable();)
_ASSERTE(pGenericMDescInRepMT->GetMethodTable() == pCanonMT);
if (getWrappedCode)
{
_ASSERTE(pWrappedMD == NULL);
_ASSERTE(pExactMT->IsCanonicalMethodTable());
_ASSERTE(pCanonMT == pExactMT);
_ASSERTE(pExactMT->IsSharedByGenericInstantiations() || ClassLoader::IsSharableInstantiation(methodInst));
}
InstantiatedMethodDesc *pNewMD;
//@todo : move this into the domain
Module * pExactMDLoaderModule = ClassLoader::ComputeLoaderModule(pExactMT, pGenericMDescInRepMT->GetMemberDef(), methodInst);
LoaderAllocator * pAllocator = pExactMDLoaderModule->GetLoaderAllocator();
// Create LoaderAllocator to LoaderAllocator links for members of the instantiations of this method
pAllocator->EnsureInstantiation(pExactMT->GetLoaderModule(), pExactMT->GetInstantiation());
pAllocator->EnsureInstantiation(pGenericMDescInRepMT->GetLoaderModule(), methodInst);
{
// Acquire crst to prevent tripping up other threads searching in the same hashtable
CrstHolder ch(&pExactMDLoaderModule->m_InstMethodHashTableCrst);
// Check whether another thread beat us to it!
pNewMD = FindLoadedInstantiatedMethodDesc(pExactMT,
pGenericMDescInRepMT->GetMemberDef(),
methodInst,
getWrappedCode);
// Crst goes out of scope here
// We don't need to hold the crst while we build the MethodDesc, but we reacquire it later
}
if (pNewMD != NULL)
{
pNewMD->CheckRestore();
}
else
{
TypeHandle *pInstOrPerInstInfo = NULL;
DictionaryLayout *pDL = NULL;
DWORD infoSize = 0;
AllocMemTracker amt;
if (!methodInst.IsEmpty())
{
if (pWrappedMD)
{
if (pWrappedMD->IsSharedByGenericMethodInstantiations())
{
// Note that it is possible for the dictionary layout to be expanded in size by other threads while we're still
// creating this method. In other words: this method will have a smaller dictionary that its layout. This is not a
// problem however because whenever we need to load a value from the dictionary of this method beyond its size, we
// will expand the dictionary at that point.
pDL = pWrappedMD->AsInstantiatedMethodDesc()->GetDictLayoutRaw();
}
}
else if (getWrappedCode)
{
pDL = DictionaryLayout::Allocate(NUM_DICTIONARY_SLOTS, pAllocator, &amt);
#ifdef _DEBUG
{
SString name;
TypeString::AppendMethodDebug(name, pGenericMDescInRepMT);
DWORD dictionarySlotSize;
DWORD dictionaryAllocSize = DictionaryLayout::GetDictionarySizeFromLayout(pGenericMDescInRepMT->GetNumGenericMethodArgs(), pDL, &dictionarySlotSize);
LOG((LF_JIT, LL_INFO1000, "GENERICS: Created new dictionary layout for dictionary of slot size %d / alloc size %d for %s\n",
dictionarySlotSize, dictionaryAllocSize, name.GetUTF8()));
}
#endif // _DEBUG
}
// Allocate space for the instantiation and dictionary
DWORD allocSize = DictionaryLayout::GetDictionarySizeFromLayout(methodInst.GetNumArgs(), pDL, &infoSize);
pInstOrPerInstInfo = (TypeHandle*)(void*)amt.Track(pAllocator->GetHighFrequencyHeap()->AllocMem(S_SIZE_T(allocSize)));
for (DWORD i = 0; i < methodInst.GetNumArgs(); i++)
pInstOrPerInstInfo[i] = methodInst[i];
if (pDL != NULL)
{
_ASSERTE(pDL->GetMaxSlots() > 0);
// Has to be at least larger than the first slots containing the instantiation arguments,
// and the slot with size information. Otherwise, we shouldn't really have a size slot
_ASSERTE(infoSize > sizeof(TypeHandle*) * (methodInst.GetNumArgs() + 1));
DWORD* pDictSizeSlot = (DWORD*)(pInstOrPerInstInfo + methodInst.GetNumArgs());
*pDictSizeSlot = infoSize;
}
}
// Create a new singleton chunk for the new instantiated method descriptor
// Notice that we've passed in the method table pointer; this gets
// used in some of the subsequent setup methods for method descs.
//
pNewMD = (InstantiatedMethodDesc*) (CreateMethodDesc(pAllocator,
pExactMT,
pGenericMDescInRepMT,
mcInstantiated,
!pWrappedMD, // This is pesimistic estimate for fNativeCodeSlot
&amt));
// Initialize the MD the way it needs to be
if (pWrappedMD)
{
pNewMD->SetupWrapperStubWithInstantiations(pWrappedMD, methodInst.GetNumArgs(), pInstOrPerInstInfo);
_ASSERTE(pNewMD->IsInstantiatingStub());
}
else if (getWrappedCode)
{
pNewMD->SetupSharedMethodInstantiation(methodInst.GetNumArgs(), pInstOrPerInstInfo, pDL);
_ASSERTE(!pNewMD->IsInstantiatingStub());
}
else
{
pNewMD->SetupUnsharedMethodInstantiation(methodInst.GetNumArgs(), pInstOrPerInstInfo);
}
// Check that whichever field holds the inst. got setup correctly
_ASSERTE((PVOID)pNewMD->GetMethodInstantiation().GetRawArgs() == (PVOID)pInstOrPerInstInfo);
pNewMD->SetTemporaryEntryPoint(pAllocator, &amt);
{
// The canonical instantiation is exempt from constraint checks. It's used as the basis
// for all other reference instantiations so we can't not load it. The Canon type is
// not visible to users so it can't be abused.
BOOL fExempt =
TypeHandle::IsCanonicalSubtypeInstantiation(methodInst) ||
TypeHandle::IsCanonicalSubtypeInstantiation(pNewMD->GetClassInstantiation());
if (!fExempt)
{
pNewMD->SatisfiesMethodConstraints(TypeHandle(pExactMT), TRUE);
}
}
// OK, now we have a candidate MethodDesc.
{
CrstHolder ch(&pExactMDLoaderModule->m_InstMethodHashTableCrst);
// We checked before, but make sure again that another thread didn't beat us to it!
InstantiatedMethodDesc *pOldMD = FindLoadedInstantiatedMethodDesc(pExactMT,
pGenericMDescInRepMT->GetMemberDef(),
methodInst,
getWrappedCode);
if (pOldMD == NULL)
{
// No one else got there first, our MethodDesc wins.
amt.SuppressRelease();
#ifdef _DEBUG
SString name;
TypeString::AppendMethodDebug(name, pNewMD);
const char* pDebugNameUTF8 = name.GetUTF8();
const char* verb = "Created";
if (pWrappedMD)
LOG((LF_CLASSLOADER, LL_INFO1000,
"GENERICS: %s instantiating-stub method desc %s with dictionary size %d\n",
verb, pDebugNameUTF8, infoSize));
else
LOG((LF_CLASSLOADER, LL_INFO1000,
"GENERICS: %s instantiated method desc %s\n",
verb, pDebugNameUTF8));
S_SIZE_T safeLen = S_SIZE_T(strlen(pDebugNameUTF8))+S_SIZE_T(1);
if(safeLen.IsOverflow()) COMPlusThrowHR(COR_E_OVERFLOW);
size_t len = safeLen.Value();
pNewMD->m_pszDebugMethodName = (char*) (void*)pAllocator->GetLowFrequencyHeap()->AllocMem(safeLen);
_ASSERTE(pNewMD->m_pszDebugMethodName);
strcpy_s((char *) pNewMD->m_pszDebugMethodName, len, pDebugNameUTF8);
pNewMD->m_pszDebugClassName = pExactMT->GetDebugClassName();
pNewMD->m_pszDebugMethodSignature = (LPUTF8)pNewMD->m_pszDebugMethodName;
#endif // _DEBUG
// Generic methods can't be varargs. code:MethodTableBuilder::ValidateMethods should have checked it.
_ASSERTE(!pNewMD->IsVarArg());
// Verify that we are not creating redundant MethodDescs
_ASSERTE(!pNewMD->IsTightlyBoundToMethodTable());
// The method desc is fully set up; now add to the table
InstMethodHashTable* pTable = pExactMDLoaderModule->GetInstMethodHashTable();
pTable->InsertMethodDesc(pNewMD);
}
else
pNewMD = pOldMD;
// CrstHolder goes out of scope here
}
}
RETURN pNewMD;
}
// Calling this method is equivalent to
// FindOrCreateAssociatedMethodDesc(pCanonicalMD, pExactMT, FALSE, Instantiation(), FALSE, TRUE)
// except that it also creates InstantiatedMethodDescs based on shared class methods. This is
// convenient for interop where, unlike ordinary managed methods, marshaling stubs for say Foo<string>
// and Foo<object> look very different and need separate representation.
InstantiatedMethodDesc*
InstantiatedMethodDesc::FindOrCreateExactClassMethod(MethodTable *pExactMT,
MethodDesc *pCanonicalMD)
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
PRECONDITION(!pExactMT->IsSharedByGenericInstantiations());
PRECONDITION(pCanonicalMD->IsSharedByGenericInstantiations());
}
CONTRACTL_END;
InstantiatedMethodDesc *pInstMD = FindLoadedInstantiatedMethodDesc(pExactMT,
pCanonicalMD->GetMemberDef(),
Instantiation(),
FALSE);
if (pInstMD == NULL)
{
// create a new MD if not found
pInstMD = NewInstantiatedMethodDesc(pExactMT,
pCanonicalMD,
pCanonicalMD,
Instantiation(),
FALSE);
}
return pInstMD;
}
// N.B. it is not guarantee that the returned InstantiatedMethodDesc is restored.
// It is the caller's responsibility to call CheckRestore on the returned value.
/* static */
InstantiatedMethodDesc*
InstantiatedMethodDesc::FindLoadedInstantiatedMethodDesc(MethodTable *pExactOrRepMT,
mdMethodDef methodDef,
Instantiation methodInst,
BOOL getWrappedCode)
{
CONTRACT(InstantiatedMethodDesc *)
{
THROWS;
GC_NOTRIGGER;
FORBID_FAULT;
PRECONDITION(CheckPointer(pExactOrRepMT));
// All wrapped method descriptors (except BoxedEntryPointStubs, which don't use this path) are
// canonical and exhibit some kind of code sharing.
PRECONDITION(!getWrappedCode || pExactOrRepMT->IsCanonicalMethodTable());
PRECONDITION(!getWrappedCode || pExactOrRepMT->IsSharedByGenericInstantiations() || ClassLoader::IsSharableInstantiation(methodInst));
// Unboxing stubs are dealt with separately in FindOrCreateAssociatedMethodDesc. This should
// probably be streamlined...
POSTCONDITION(!RETVAL || !RETVAL->IsUnboxingStub());
// All wrapped method descriptors (except BoxedEntryPointStubs, which don't use this path) take an inst arg.
// The only ones that don't should have been found in the type's meth table.
POSTCONDITION(!getWrappedCode || !RETVAL || RETVAL->RequiresInstArg());
}
CONTRACT_END
// First look in the table for the runtime loader module in case someone created it before any
// zap modules got loaded
Module *pLoaderModule = ClassLoader::ComputeLoaderModule(pExactOrRepMT, methodDef, methodInst);
InstMethodHashTable* pTable = pLoaderModule->GetInstMethodHashTable();
MethodDesc *resultMD = pTable->FindMethodDesc(TypeHandle(pExactOrRepMT),
methodDef,
FALSE /* not forceBoxedEntryPoint */,
methodInst,
getWrappedCode);
if (resultMD != NULL)
RETURN((InstantiatedMethodDesc*) resultMD);
RETURN(NULL);
}
// Given a method descriptor, find (or create) an instantiated
// method descriptor or BoxedEntryPointStub associated with that method
// and a particular instantiation of any generic method arguments. Also check
// the method instantiation is valid.
//
// This routine also works for non-generic methods - it will be fast
// in most cases. In this case nothing in particular
// occurs except for static methods in shared generic classes, where an
// instantiating stub is needed.
//
// The generic parameters provided are only those for the generic method.
// pExactMT should be used to specify any class parameters.
//
// Unboxing stubs
// --------------
//
// These are required to provide callable addresses with a uniform calling convention
// for all methods on a boxed value class object. There are a wide range of possible
// methods:
// 1 virtual, non-generic instance methods
// 2 non-virtual, non-generic instance methods
// 3 virtual, generic instance methods
// 4 non-virtual, generic instance methods
// There is no substantial difference between case 3 and case 4: the only times
// when BoxedEntryPointStubs are used for non-virtual methods are when calling a delegate or
// making a reflection call.
//
// The only substantial difference between 1 and 2 is that the stubs are stored in
// different places - we are forced to create the BoxedEntryPointStubs for (1) at class
// creation time (they form part of the vtable and dispatch maps). Hence these
// stubs are "owned" by method tables. We store all other stubs in the AssociatedMethTable.
//
// Unboxing stubs and generics
// ---------------------------
//
// Generics code sharing complicates matters. The typical cases are where the struct
// is in a shared-codegenerics struct such as
//
// struct Pair<string,object>
//
// which shares code with other types such as Pair<object,object>. All the code that ends up
// being run for all the methods in such a struct takes an instantiation parameter, i.e.
// is RequiresInstArg(), a non-uniform calling convention. We obviously can't give these out as
// targets of delegate calls. Hence we have to wrap this shared code in various stubs in
// order to get the right type context parameter provided to the shared code.
//
// Unboxing stubs on shared-code generic structs, e.g. Pair<object,string>,
// acquire the class-portion of their type context from the "this" pointer.
//
// Thus there are two flavours of BoxedEntryPointStubs:
//
// - Methods that are not themselves generic:
//
// These wrap possibly-shared code (hence allowInstParam == TRUE).
//
// These directly call the possible-shared code for the instance method. This code
// can have the following calling conventions:
// - RequiresMethodTableInstArg() (if pMT->SharedByGenericInstantiations())
// - Uniform (if !pMT->SharedByGenericInstantiations())
//
// Thus if the code they are
//
// - Methods that are themselves generic:
//
// These wrap unshared code (hence allowInstParam == FALSE):
//
// These are always invoked by slow paths (how often do you use a generic method in a struct?),
// such as JIT_VirtualFunctionPointer or a reflection call. These paths eventually
// use FindOrCreateAssociatedMethodDesc to piece together the exact instantiation provided by the "this"
// pointer with the exact instantiation provided by the wrapped method pointer.
//
// These call a stub for the instance method which provides the instantiation
// context, possibly in turn calling further shared code. This stub will
// always be !RequiresInstArg()
//
// If the method being called is aMethod calls via BoxedEntryPointStubs
//
// Remotable methods
// -----------------
//
// Remoting has high requirements for method descs passed to it (i.e. the method desc that represents the client "view" of the
// method to be called on the real server object). Since the identity of the method call is serialized and passed on the wire before
// be resolved into the real target method on the server remoting needs to be able to extract exact instantiation information from
// its inputs (a method desc and a this pointer).
//
// To that end generic methods should always be passed via an instantiating stub (i.e. set allowInstParam to FALSE when calling
// FindOrCreateAssociatedMethodDesc).
//
// There's a more subtle problem though. If the client method call is via a non-generic method on a generic interface we won't have
// enough information to serialize the call. That's because such methods don't have instantiated method descs by default (these are
// weighty structures and most of the runtime would never use the extra information). The this pointer doesn't help provide the
// additional information in this case (consider the case of a class that implements both IFoo<String> and IFoo<Random>).
//
// So instead we create instantiated interface method descs on demand (i.e. during stub-based interface dispatch). Setting the
// forceRemotableMethod predicate to TRUE below will ensure this (it's a no-op for methods that don't match this pattern, so can be
// freely set to true for all calls intended to produce a remotable ready method). This characteristic of a methoddesc that is fully
// descriptive of the method and class used is also necessary in certain places in reflection. In particular, it is known to be needed
// for the Delegate.CreateDelegate logic.
//
// allowCreate may be set to FALSE to enforce that the method searched
// should already be in existence - thus preventing creation and GCs during
// inappropriate times.
#ifdef _PREFAST_
#pragma warning(push)
#pragma warning(disable:21000) // Suppress PREFast warning about overly large function
#endif
/* static */
MethodDesc*
MethodDesc::FindOrCreateAssociatedMethodDesc(MethodDesc* pDefMD,
MethodTable *pExactMT,
BOOL forceBoxedEntryPoint,
Instantiation methodInst,
BOOL allowInstParam,
BOOL forceRemotableMethod,
BOOL allowCreate,
ClassLoadLevel level)
{
CONTRACT(MethodDesc*)
{
THROWS;
if (allowCreate) { GC_TRIGGERS; } else { GC_NOTRIGGER; }
INJECT_FAULT(COMPlusThrowOM(););
PRECONDITION(CheckPointer(pDefMD));
PRECONDITION(CheckPointer(pExactMT));
// If the method descriptor belongs to a generic type then
// the input exact type must be an instantiation of that type.
// DISABLED PRECONDITION - too strict - the classes may be in
// a subtype relation to each other.
//
// PRECONDITION(!pDefMD->HasClassInstantiation() || pDefMD->GetMethodTable()->HasSameTypeDefAs(pExactMT));
// You may only request an BoxedEntryPointStub for an instance method on a value type
PRECONDITION(!forceBoxedEntryPoint || pExactMT->IsValueType());
PRECONDITION(!forceBoxedEntryPoint || !pDefMD->IsStatic());
// For remotable methods we better not be allowing instantiation parameters.
PRECONDITION(!forceRemotableMethod || !allowInstParam);
POSTCONDITION(((RETVAL == NULL) && !allowCreate) || CheckPointer(RETVAL));
POSTCONDITION(((RETVAL == NULL) && !allowCreate) || forceBoxedEntryPoint || !RETVAL->IsUnboxingStub());
POSTCONDITION(((RETVAL == NULL) && !allowCreate) || allowInstParam || !RETVAL->RequiresInstArg());
}
CONTRACT_END;
// Quick exit for the common cases where the result is the same as the primary MD we are given
if (!pDefMD->HasClassOrMethodInstantiation() &&
methodInst.IsEmpty() &&
!forceBoxedEntryPoint &&
!pDefMD->IsUnboxingStub())
{
// Make sure that pDefMD->GetMethodTable() and pExactMT are related types even
// if we took the fast path.
_ASSERTE(pDefMD->IsArray() || pDefMD->GetExactDeclaringType(pExactMT) != NULL);
RETURN pDefMD;
}
// Get the version of the method desc. for the instantiated shared class, e.g.
// e.g. if pDefMD == List<T>.m()
// pExactMT = List<string>
// then pMDescInCanonMT = List<object>.m()
// or
// e.g. if pDefMD == List<T>.m<U>()
// pExactMT = List<string>
// then pMDescInCanonMT = List<object>.m<U>()
MethodDesc * pMDescInCanonMT = pDefMD;
// Some callers pass a pExactMT that is a subtype of a parent type of pDefMD.
// Find the actual exact parent of pDefMD.
pExactMT = pDefMD->GetExactDeclaringType(pExactMT);
if (pExactMT == NULL)
{
_ASSERTE(false);
COMPlusThrowHR(COR_E_TYPELOAD);
}
if (pDefMD->HasClassOrMethodInstantiation() || !methodInst.IsEmpty())
{
// General checks related to generics: arity (if any) must match and generic method
// instantiation (if any) must be well-formed.
if (pDefMD->GetNumGenericMethodArgs() != methodInst.GetNumArgs() ||
!Generics::CheckInstantiation(methodInst))
{
COMPlusThrowHR(COR_E_BADIMAGEFORMAT);
}
pMDescInCanonMT = pExactMT->GetCanonicalMethodTable()->GetParallelMethodDesc(pDefMD);
if (!allowCreate && !pMDescInCanonMT->GetMethodTable()->IsFullyLoaded())
{
RETURN(NULL);
}
pMDescInCanonMT->CheckRestore(level);
}
// This case covers nearly all "normal" (i.e. non-associate) MethodDescs. Just return
// the MethodDesc in the canonical method table.
//
// Also, it will be taken for methods which acquire their type context from the "this" parameter
// - we don't need instantiating stubs for these.
if ( methodInst.IsEmpty()
&& (allowInstParam || !pMDescInCanonMT->RequiresInstArg())
&& (forceBoxedEntryPoint == pMDescInCanonMT->IsUnboxingStub())
&& (!forceRemotableMethod || !pMDescInCanonMT->IsInterface()
|| !pMDescInCanonMT->GetMethodTable()->IsSharedByGenericInstantiations()) )
{
RETURN(pMDescInCanonMT);
}
// Unboxing stubs
else if (forceBoxedEntryPoint)
{
// This assert isn't quite right, for example the repro from NDPWhidbey 18737
// fires it, because we fetch an BoxedEntryPointStub for a virtual method on a struct
// when the uninstantiated MethodDesc for the generic virtual method actually
// qualifies as an BoxedEntryPointStub... Hence we weaken the assert a little.
//
// _ASSERTE(!pDefMD->IsUnboxingStub());
// _ASSERTE(pDefMD->IsGenericMethodDefinition() || !pDefMD->IsUnboxingStub());
//Unboxing stubs for non-generic methods and generic methods are
// subtly different... For non-generic methods we can look in the
// shared vtable, and then go to the hash table only if needed.
// Furthermore even if we have to go to hash table we still base
// the BoxedEntryPointStub on an unerlying _shared_ method descriptor.
//
// For generic methods we must build an BoxedEntryPointStub that calls an
// underlying instantiating stub. The underlying instantiating stub
// will be an _exact_ method descriptor.
MethodDesc *pResultMD;
if (methodInst.IsEmpty())
{
// First search for the unboxing MD in the shared vtable for the value type
pResultMD = FindTightlyBoundUnboxingStub(pMDescInCanonMT);
// Verify that we get the same result by alternative method. There is a possibility
// that there is no associated unboxing stub, and FindTightlyBoundUnboxingStub takes
// this into account but the _DEBUG version does not, so only use it if the method
// returned is actually different.
_ASSERTE(pResultMD == pMDescInCanonMT ||
pResultMD == FindTightlyBoundUnboxingStub_DEBUG(pMDescInCanonMT));
if (pResultMD != NULL)
{
_ASSERTE(pResultMD->GetMethodTable()->IsFullyLoaded());
RETURN(pResultMD);
}
MethodTable *pRepMT = pMDescInCanonMT->GetMethodTable();
mdMethodDef methodDef = pDefMD->GetMemberDef();
Module *pLoaderModule = ClassLoader::ComputeLoaderModule(pRepMT, methodDef, methodInst);
LoaderAllocator* pAllocator=pLoaderModule->GetLoaderAllocator();
InstMethodHashTable* pTable = pLoaderModule->GetInstMethodHashTable();
// If we didn't find it there then go to the hash table
pResultMD = pTable->FindMethodDesc(TypeHandle(pRepMT),
methodDef,
TRUE /* forceBoxedEntryPoint */,
Instantiation(),
FALSE /* no inst param */);
// If we didn't find it then create it...
if (!pResultMD)
{
// !allowCreate ==> GC_NOTRIGGER ==> no entering Crst
if (!allowCreate)
{
RETURN(NULL);
}
CrstHolder ch(&pLoaderModule->m_InstMethodHashTableCrst);
// Check whether another thread beat us to it!
pResultMD = pTable->FindMethodDesc(TypeHandle(pRepMT),
methodDef,
TRUE,
Instantiation(),
FALSE);
if (pResultMD == NULL)
{
AllocMemTracker amt;
pResultMD = CreateMethodDesc(pAllocator,
pRepMT,
pMDescInCanonMT,
mcInstantiated,
FALSE /* fNativeCodeSlot */,
&amt);
// Indicate that this is a stub method which takes a BOXed this pointer.
// An BoxedEntryPointStub may still be an InstantiatedMethodDesc
pResultMD->SetIsUnboxingStub();
pResultMD->AsInstantiatedMethodDesc()->SetupWrapperStubWithInstantiations(pMDescInCanonMT, NULL, NULL);
pResultMD->SetTemporaryEntryPoint(pAllocator, &amt);
amt.SuppressRelease();
// Verify that we are not creating redundant MethodDescs
_ASSERTE(!pResultMD->IsTightlyBoundToMethodTable());
// Add it to the table
pTable->InsertMethodDesc(pResultMD);
}
// CrstHolder goes out of scope here
}
}
else
{
mdMethodDef methodDef = pDefMD->GetMemberDef();
Module *pLoaderModule = ClassLoader::ComputeLoaderModule(pExactMT, methodDef, methodInst);
LoaderAllocator* pAllocator = pLoaderModule->GetLoaderAllocator();
InstMethodHashTable* pTable = pLoaderModule->GetInstMethodHashTable();
// First check the hash table...
pResultMD = pTable->FindMethodDesc(TypeHandle(pExactMT),
methodDef,
TRUE, /* forceBoxedEntryPoint */
methodInst,
FALSE /* no inst param */);
if (!pResultMD)
{
// !allowCreate ==> GC_NOTRIGGER ==> no entering Crst
if (!allowCreate)
{
RETURN(NULL);
}
// Recursively get the non-unboxing instantiating stub. Thus we chain an unboxing
// stub with an instantiating stub.
MethodDesc* pNonUnboxingStub=
MethodDesc::FindOrCreateAssociatedMethodDesc(pDefMD,
pExactMT,
FALSE /* not Unboxing */,
methodInst,
FALSE);
_ASSERTE(pNonUnboxingStub->GetClassification() == mcInstantiated);
_ASSERTE(!pNonUnboxingStub->RequiresInstArg());
_ASSERTE(!pNonUnboxingStub->IsUnboxingStub());
// Enter the critical section *after* we've found or created the non-unboxing instantiating stub (else we'd have a race,
// and its possible that the non-unboxing instantiating stub may be in a different loader module than pLoaderModule
// which would cause a Crst lock level violation
CrstHolder ch(&pLoaderModule->m_InstMethodHashTableCrst);
// Check whether another thread beat us to it!
pResultMD = pTable->FindMethodDesc(TypeHandle(pExactMT),
methodDef,
TRUE, /* forceBoxedEntryPoint */
methodInst,
FALSE /* no inst param */);
if (pResultMD == NULL)
{
AllocMemTracker amt;
_ASSERTE(pDefMD->GetClassification() == mcInstantiated);
pResultMD = CreateMethodDesc(pAllocator,
pExactMT,
pNonUnboxingStub,
mcInstantiated,
FALSE /* fNativeCodeSlot */,
&amt);
pResultMD->SetIsUnboxingStub();
pResultMD->AsInstantiatedMethodDesc()->SetupWrapperStubWithInstantiations(pNonUnboxingStub,
pNonUnboxingStub->GetNumGenericMethodArgs(),
(TypeHandle *)pNonUnboxingStub->GetMethodInstantiation().GetRawArgs());
pResultMD->SetTemporaryEntryPoint(pAllocator, &amt);
amt.SuppressRelease();
// Verify that we are not creating redundant MethodDescs
_ASSERTE(!pResultMD->IsTightlyBoundToMethodTable());
pTable->InsertMethodDesc(pResultMD);
}
// CrstHolder goes out of scope here
}
}
_ASSERTE(pResultMD);
if (!allowCreate && !pResultMD->GetMethodTable()->IsFullyLoaded())
{
RETURN(NULL);
}
pResultMD->CheckRestore(level);
_ASSERTE(pResultMD->IsUnboxingStub());
_ASSERTE(!pResultMD->IsInstantiatingStub());
RETURN(pResultMD);
}
// Now all generic method instantiations and static/shared-struct-instance-method wrappers...
else
{
_ASSERTE(!forceBoxedEntryPoint);
mdMethodDef methodDef = pDefMD->GetMemberDef();
Module *pModule = pDefMD->GetModule();
// Some unboxed entry points are attached to canonical method tables. This is because
// we have to fill in vtables and/or dispatch maps at load time,
// and boxed entry points are created to do this. (note vtables and dispatch maps
// are only created for canonical instantiations). These boxed entry points
// in turn refer to unboxed entry points.
if (// Check if we're looking for something at the canonical instantiation
(allowInstParam || pExactMT->IsCanonicalMethodTable()) &&
// Only value types have BoxedEntryPointStubs in the canonical method table
pExactMT->IsValueType() &&
// The only generic methods whose BoxedEntryPointStubs are in the canonical method table
// are those open MethodDescs at the "typical" isntantiation, e.g.
// VC<int>.m<T>
// <NICE> This is probably actually not needed </NICE>
ClassLoader::IsTypicalInstantiation(pModule, methodDef, methodInst)
)
{
MethodDesc * pResultMD = FindTightlyBoundWrappedMethodDesc(pMDescInCanonMT);
// Verify that we get the same result by alternative method. There is a possibility
// that this is not an unboxing stub, and FindTightlyBoundWrappedMethodDesc takes
// this into account but the _DEBUG version does not, so only use it if the method
// returned is actually different.
_ASSERTE(pResultMD == pMDescInCanonMT ||
pResultMD == FindTightlyBoundWrappedMethodDesc_DEBUG(pMDescInCanonMT));
if (pResultMD != NULL)
{
_ASSERTE(pResultMD->GetMethodTable()->IsFullyLoaded());
if (allowInstParam || !pResultMD->RequiresInstArg())
{
RETURN(pResultMD);
}
}
}
// Are either the generic type arguments or the generic method arguments shared?
BOOL sharedInst =
pExactMT->GetCanonicalMethodTable()->IsSharedByGenericInstantiations()
|| ClassLoader::IsSharableInstantiation(methodInst);
// Is it the "typical" instantiation in the correct type that does not require wrapper?
if (!sharedInst &&
pExactMT == pMDescInCanonMT->GetMethodTable() &&
ClassLoader::IsTypicalInstantiation(pModule, methodDef, methodInst))
{
_ASSERTE(!pMDescInCanonMT->IsUnboxingStub());
RETURN(pMDescInCanonMT);
}
// OK, so we now know the thing we're looking for can only be found in the MethodDesc table.
// If getWrappedCode == true, we are looking for a wrapped MethodDesc
BOOL getWrappedCode = allowInstParam && sharedInst;
BOOL getWrappedThenStub = !allowInstParam && sharedInst;
CQuickBytes qbRepInst;
TypeHandle *repInst = NULL;
if (getWrappedCode || getWrappedThenStub)
{
// Canonicalize the type arguments.
DWORD cbAllocaSize = 0;
if (!ClrSafeInt<DWORD>::multiply(methodInst.GetNumArgs(), sizeof(TypeHandle), cbAllocaSize))
ThrowHR(COR_E_OVERFLOW);
repInst = reinterpret_cast<TypeHandle *>(qbRepInst.AllocThrows(cbAllocaSize));
for (DWORD i = 0; i < methodInst.GetNumArgs(); i++)
{
repInst[i] = ClassLoader::CanonicalizeGenericArg(methodInst[i]);
}
}
// <NICE> These paths can probably be merged together more nicely, and the lookup-lock-lookup pattern made much
// more obvious </NICE>
InstantiatedMethodDesc *pInstMD;
if (getWrappedCode)
{
// Get the underlying shared code using the canonical instantiations
pInstMD =
InstantiatedMethodDesc::FindLoadedInstantiatedMethodDesc(pExactMT->GetCanonicalMethodTable(),
methodDef,
Instantiation(repInst, methodInst.GetNumArgs()),
TRUE);
// No - so create one.
if (pInstMD == NULL)
{
if (!allowCreate)
{
RETURN(NULL);
}
pInstMD = InstantiatedMethodDesc::NewInstantiatedMethodDesc(pExactMT->GetCanonicalMethodTable(),
pMDescInCanonMT,
NULL,
Instantiation(repInst, methodInst.GetNumArgs()),
TRUE);
}
}
else if (getWrappedThenStub)
{
// See if we've already got the instantiated method desc for this one.
pInstMD =
InstantiatedMethodDesc::FindLoadedInstantiatedMethodDesc(pExactMT,
methodDef,
methodInst,
FALSE);
// No - so create one. Go fetch the shared one first
if (pInstMD == NULL)
{
if (!allowCreate)
{
RETURN(NULL);
}
// This always returns the shared code. Repeat the original call except with
// approximate params and allowInstParam=true
MethodDesc* pWrappedMD = FindOrCreateAssociatedMethodDesc(pDefMD,
pExactMT->GetCanonicalMethodTable(),
FALSE,
Instantiation(repInst, methodInst.GetNumArgs()),
/* allowInstParam */ TRUE,
/* forceRemotableMethod */ FALSE,
/* allowCreate */ TRUE,
/* level */ level);
_ASSERTE(pWrappedMD->IsSharedByGenericInstantiations());
_ASSERTE(!methodInst.IsEmpty() || !pWrappedMD->IsSharedByGenericMethodInstantiations());
pInstMD = InstantiatedMethodDesc::NewInstantiatedMethodDesc(pExactMT,
pMDescInCanonMT,
pWrappedMD,
methodInst,
FALSE);
}
}
else
{
// See if we've already got the instantiated method desc for this one.
// If looking for shared code use the representative inst.
pInstMD =
InstantiatedMethodDesc::FindLoadedInstantiatedMethodDesc(pExactMT,
methodDef,
methodInst,
FALSE);
// No - so create one.
if (pInstMD == NULL)
{
if (!allowCreate)
{
RETURN(NULL);
}
pInstMD = InstantiatedMethodDesc::NewInstantiatedMethodDesc(pExactMT,
pMDescInCanonMT,
NULL,
methodInst,
FALSE);
}
}
_ASSERTE(pInstMD);
if (!allowCreate && !pInstMD->GetMethodTable()->IsFullyLoaded())
{
RETURN(NULL);
}
pInstMD->CheckRestore(level);
RETURN(pInstMD);
}
}
#ifdef _PREFAST_
#pragma warning(pop)
#endif
// Normalize the methoddesc for reflection
/*static*/ MethodDesc* MethodDesc::FindOrCreateAssociatedMethodDescForReflection(
MethodDesc *pMethod,
TypeHandle instType,
Instantiation methodInst)
{
CONTRACTL {
THROWS;
GC_TRIGGERS; // Because allowCreate is TRUE
PRECONDITION(CheckPointer(pMethod));
}
CONTRACTL_END;
MethodDesc *pInstMD = pMethod;
// no stubs for TypeDesc
if (instType.IsTypeDesc())
return pInstMD;
MethodTable* pMT = instType.AsMethodTable();
if (!methodInst.IsEmpty())
{
// method.BindGenericParameters() was called and we need to retrieve an instantiating stub
// pMethod is not necessarily a generic method definition, ResolveMethod could pass in an
// instantiated generic method.
_ASSERTE(pMethod->HasMethodInstantiation());
if (methodInst.GetNumArgs() != pMethod->GetNumGenericMethodArgs())
COMPlusThrow(kArgumentException);
// we need unboxing stubs for virtual methods on value types
pInstMD = MethodDesc::FindOrCreateAssociatedMethodDesc(
pMethod,
pMT,
instType.IsValueType() && pMethod->IsVirtual(),
methodInst,
FALSE, /* no allowInstParam */
TRUE /* force remotable method (i.e. inst wrappers for non-generic methods on generic interfaces) */);
}
else if ( !pMethod->HasMethodInstantiation() &&
( instType.IsValueType() ||
( instType.HasInstantiation() &&
!instType.IsGenericTypeDefinition() &&
( instType.IsInterface() || pMethod->IsStatic() ) ) ) )
{
//
// Called at MethodInfos cache creation
// the method is either a normal method or a generic method definition
// Also called at MethodBase.GetMethodBaseFromHandle
// the method is either a normal method, a generic method definition, or an instantiated generic method
// Needs an instantiating stub if
// - non generic static method on a generic class
// - non generic instance method on a struct
// - non generic method on a generic interface
//
// we need unboxing stubs for virtual methods on value types unless the method is generic
BOOL fNeedUnboxingStub = instType.IsValueType() && pMethod->IsVirtual();
pInstMD = MethodDesc::FindOrCreateAssociatedMethodDesc(
pMethod, /* the original MD */
pMT, /* the method table */
fNeedUnboxingStub, /* create boxing stub */
Instantiation(), /* no generic instantiation */
FALSE, /* no allowInstParam */
TRUE /* force remotable method (i.e. inst wrappers for non-generic methods on generic interfaces) */);
}
return pInstMD;
}
// Given a typical method desc (i.e. instantiated at formal type
// parameters if it is a generic method or lives in a generic class),
// instantiate any type parameters at <__Canon>
//
// NOTE: If allowCreate is FALSE, typically you must also set ENABLE_FORBID_GC_LOADER_USE_IN_THIS_SCOPE()
// allowCreate may be set to FALSE to enforce that the method searched
// should already be in existence - thus preventing creation and GCs during
// inappropriate times.
//
MethodDesc * MethodDesc::FindOrCreateTypicalSharedInstantiation(BOOL allowCreate /* = TRUE */)
{
CONTRACT(MethodDesc*)
{
THROWS;
GC_TRIGGERS;
PRECONDITION(IsTypicalMethodDefinition());
POSTCONDITION(CheckPointer(RETVAL));
POSTCONDITION(RETVAL->IsTypicalSharedInstantiation());
}
CONTRACT_END
MethodDesc *pMD = this;
MethodTable *pMT = pMD->GetMethodTable();
// First instantiate the declaring type at <__Canon,...,__Canon>
DWORD nGenericClassArgs = pMT->GetNumGenericArgs();
DWORD dwAllocSize = 0;
if (!ClrSafeInt<DWORD>::multiply(sizeof(TypeHandle), nGenericClassArgs, dwAllocSize))
ThrowHR(COR_E_OVERFLOW);
CQuickBytes qbGenericClassArgs;
TypeHandle* pGenericClassArgs = reinterpret_cast<TypeHandle*>(qbGenericClassArgs.AllocThrows(dwAllocSize));
for (DWORD i = 0; i < nGenericClassArgs; i++)
{
pGenericClassArgs[i] = TypeHandle(g_pCanonMethodTableClass);
}
pMT = ClassLoader::LoadGenericInstantiationThrowing(pMT->GetModule(),
pMT->GetCl(),
Instantiation(pGenericClassArgs, nGenericClassArgs),
allowCreate ? ClassLoader::LoadTypes : ClassLoader::DontLoadTypes
).GetMethodTable();
if (pMT == NULL)
{
_ASSERTE(!allowCreate);
return NULL;
}
// Now instantiate the method at <__Canon,...,__Canon>, creating the shared code.
// This will not create an instantiating stub just yet.
DWORD nGenericMethodArgs = pMD->GetNumGenericMethodArgs();
CQuickBytes qbGenericMethodArgs;
TypeHandle *genericMethodArgs = NULL;
// The rest of this method instantiates a generic method
// Instantiate at "__Canon" if a NULL "genericMethodArgs" is given
if (nGenericMethodArgs)
{
dwAllocSize = 0;
if (!ClrSafeInt<DWORD>::multiply(sizeof(TypeHandle), nGenericMethodArgs, dwAllocSize))
ThrowHR(COR_E_OVERFLOW);
genericMethodArgs = reinterpret_cast<TypeHandle*>(qbGenericMethodArgs.AllocThrows(dwAllocSize));
for (DWORD i =0; i < nGenericMethodArgs; i++)
genericMethodArgs[i] = TypeHandle(g_pCanonMethodTableClass);
}
RETURN(MethodDesc::FindOrCreateAssociatedMethodDesc(pMD,
pMT,
FALSE, /* don't get unboxing entry point */
Instantiation(genericMethodArgs, nGenericMethodArgs),
TRUE,
FALSE,
allowCreate));
}
//@GENERICSVER: Set up the typical instance (i.e., non-instantiated)
void InstantiatedMethodDesc::SetupGenericMethodDefinition(IMDInternalImport* pIMDII,
LoaderAllocator* pAllocator,
AllocMemTracker* pamTracker,
Module* pModule,
mdMethodDef tok)
{
CONTRACTL
{
THROWS;
GC_NOTRIGGER;
INJECT_FAULT(COMPlusThrowOM(););
PRECONDITION(CheckPointer(pIMDII));
PRECONDITION(CheckPointer(pAllocator));
PRECONDITION(CheckPointer(pamTracker));
PRECONDITION(CheckPointer(pModule));
}
CONTRACTL_END;
// The first field is never used
m_wFlags2 = GenericMethodDefinition | (m_wFlags2 & ~KindMask);
//@GENERICSVER: allocate space for and initialize the typical instantiation
//we share the typical instantiation among all instantiations by placing it in the generic method desc
LOG((LF_JIT, LL_INFO10000, "IMD::SGMD: Initializing typical MethodDesc this:%p\n", this));
mdGenericParam tkTyPar;
HENUMInternalHolder hEnumTyPars(pIMDII);
hEnumTyPars.EnumInit(mdtGenericParam, tok);
// Initialize the typical instantiation
DWORD numTyPars = hEnumTyPars.EnumGetCount();
if (!FitsIn<WORD>(numTyPars))
{
LPCSTR szMethodName;
if (FAILED(pIMDII->GetNameOfMethodDef(tok, &szMethodName)))
{
szMethodName = "Invalid MethodDef record";
}
pModule->GetAssembly()->ThrowTypeLoadException(szMethodName, IDS_CLASSLOAD_TOOMANYGENERICARGS);
}
m_wNumGenericArgs = static_cast<WORD>(numTyPars);
_ASSERTE(m_wNumGenericArgs > 0);
S_SIZE_T dwAllocSize = S_SIZE_T(numTyPars) * S_SIZE_T(sizeof(TypeHandle));
// the memory allocated for m_pMethInst will be freed if the declaring type fails to load
m_pPerInstInfo = (Dictionary *) pamTracker->Track(pAllocator->GetLowFrequencyHeap()->AllocMem(dwAllocSize));
TypeHandle * pInstDest = (TypeHandle *) IMD_GetMethodDictionary();
_ASSERTE(pInstDest != NULL);
{
// Protect multi-threaded access to Module.m_GenericParamToDescMap. Other threads may be loading the same type
// to break type recursion dead-locks
CrstHolder ch(&pModule->GetClassLoader()->m_AvailableTypesLock);
for (unsigned int i = 0; i < numTyPars; i++)
{
hEnumTyPars.EnumNext(&tkTyPar);
// code:Module.m_GenericParamToDescMap maps generic parameter RIDs to TypeVarTypeDesc
// instances so that we do not leak by allocating them all over again, if the declaring
// type repeatedly fails to load.
TypeVarTypeDesc* pTypeVarTypeDesc = pModule->LookupGenericParam(tkTyPar);
if (pTypeVarTypeDesc == NULL)
{
// Do NOT use pamTracker for this memory as we need it stay allocated even if the load fails.
void* mem = (void*)pAllocator->GetLowFrequencyHeap()->AllocMem(S_SIZE_T(sizeof(TypeVarTypeDesc)));
pTypeVarTypeDesc = new (mem) TypeVarTypeDesc(pModule, tok, i, tkTyPar);
pModule->StoreGenericParamThrowing(tkTyPar, pTypeVarTypeDesc);
}
pInstDest[i] = TypeHandle(pTypeVarTypeDesc);
}
}
LOG((LF_JIT, LL_INFO10000, "IMD::SGMD: Initialized typical MethodDesc. type handles: %u\n",
numTyPars));
}
void InstantiatedMethodDesc::SetupWrapperStubWithInstantiations(MethodDesc* wrappedMD,DWORD numGenericArgs, TypeHandle *pInst)
{
WRAPPER_NO_CONTRACT;
//_ASSERTE(sharedMD->IMD_IsSharedByGenericMethodInstantiations());
m_pWrappedMethodDesc = wrappedMD;
m_wFlags2 = WrapperStubWithInstantiations | (m_wFlags2 & ~KindMask);
m_pPerInstInfo = (Dictionary*)pInst;
_ASSERTE(FitsIn<WORD>(numGenericArgs));
m_wNumGenericArgs = static_cast<WORD>(numGenericArgs);
_ASSERTE(IMD_IsWrapperStubWithInstantiations());
_ASSERTE(((MethodDesc *) this)->IsInstantiatingStub() || ((MethodDesc *) this)->IsUnboxingStub());
}
// Set the instantiation in the per-inst section (this is actually a dictionary)
void InstantiatedMethodDesc::SetupSharedMethodInstantiation(DWORD numGenericArgs, TypeHandle *pPerInstInfo, DictionaryLayout *pDL)
{
WRAPPER_NO_CONTRACT;
_ASSERTE(numGenericArgs != 0);
// Initially the dictionary layout is empty
m_wFlags2 = SharedMethodInstantiation | (m_wFlags2 & ~KindMask);
m_pPerInstInfo = (Dictionary *)pPerInstInfo;
_ASSERTE(FitsIn<WORD>(numGenericArgs));
m_wNumGenericArgs = static_cast<WORD>(numGenericArgs);
m_pDictLayout = pDL;
_ASSERTE(IMD_IsSharedByGenericMethodInstantiations());
}
// Set the instantiation in the per-inst section (this is actually a dictionary)
void InstantiatedMethodDesc::SetupUnsharedMethodInstantiation(DWORD numGenericArgs, TypeHandle *pInst)
{
LIMITED_METHOD_CONTRACT;
// The first field is never used
m_wFlags2 = UnsharedMethodInstantiation | (m_wFlags2 & ~KindMask);
m_pPerInstInfo = (Dictionary *)pInst;
_ASSERTE(FitsIn<WORD>(numGenericArgs));
m_wNumGenericArgs = static_cast<WORD>(numGenericArgs);
_ASSERTE(!IsUnboxingStub());
_ASSERTE(!IsInstantiatingStub());
_ASSERTE(!IMD_IsWrapperStubWithInstantiations());
_ASSERTE(!IMD_IsSharedByGenericMethodInstantiations());
_ASSERTE(!IMD_IsGenericMethodDefinition());
}
// A type variable is bounded to some depth iff it
// has no chain of type variable bounds of that depth.
// We use this is a simple test for circularity among class and method type parameter constraints:
// the constraints on a set of n variables are well-founded iff every variable is bounded by n.
// The test is cheap for the common case that few, if any, constraints are variables.
BOOL Bounded(TypeVarTypeDesc *tyvar, DWORD depth) {
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_ANY;
PRECONDITION(CheckPointer(tyvar));
} CONTRACTL_END;
if (depth == 0)
{
return FALSE;
}
DWORD numConstraints;
TypeHandle *constraints = tyvar->GetConstraints(&numConstraints, CLASS_DEPENDENCIES_LOADED);
for (unsigned i = 0; i < numConstraints; i++)
{
TypeHandle constraint = constraints[i];
if (constraint.IsGenericVariable())
{
TypeVarTypeDesc* constraintVar = (TypeVarTypeDesc*) constraint.AsTypeDesc();
//only consider bounds between same sort of variables (VAR or MVAR)
if (tyvar->GetInternalCorElementType() == constraintVar->GetInternalCorElementType())
{
if (!Bounded(constraintVar, depth - 1))
return FALSE;
}
}
}
return TRUE;
}
void MethodDesc::LoadConstraintsForTypicalMethodDefinition(BOOL *pfHasCircularClassConstraints, BOOL *pfHasCircularMethodConstraints, ClassLoadLevel level/* = CLASS_LOADED*/)
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_ANY;
PRECONDITION(IsTypicalMethodDefinition());
PRECONDITION(CheckPointer(pfHasCircularClassConstraints));
PRECONDITION(CheckPointer(pfHasCircularMethodConstraints));
} CONTRACTL_END;
*pfHasCircularClassConstraints = FALSE;
*pfHasCircularMethodConstraints = FALSE;
// Force a load of the constraints on the type parameters
Instantiation classInst = GetClassInstantiation();
for (DWORD i = 0; i < classInst.GetNumArgs(); i++)
{
TypeVarTypeDesc* tyvar = classInst[i].AsGenericVariable();
_ASSERTE(tyvar != NULL);
tyvar->LoadConstraints(level);
}
Instantiation methodInst = GetMethodInstantiation();
for (DWORD i = 0; i < methodInst.GetNumArgs(); i++)
{
TypeVarTypeDesc* tyvar = methodInst[i].AsGenericVariable();
_ASSERTE(tyvar != NULL);
tyvar->LoadConstraints(level);
VOID DoAccessibilityCheckForConstraints(MethodTable *pAskingMT, TypeVarTypeDesc *pTyVar, UINT resIDWhy);
DoAccessibilityCheckForConstraints(GetMethodTable(), tyvar, E_ACCESSDENIED);
}
// reject circular class constraints
for (DWORD i = 0; i < classInst.GetNumArgs(); i++)
{
TypeVarTypeDesc* tyvar = classInst[i].AsGenericVariable();
_ASSERTE(tyvar != NULL);
if(!Bounded(tyvar, classInst.GetNumArgs()))
{
*pfHasCircularClassConstraints = TRUE;
}
}
// reject circular method constraints
for (DWORD i = 0; i < methodInst.GetNumArgs(); i++)
{
TypeVarTypeDesc* tyvar = methodInst[i].AsGenericVariable();
_ASSERTE(tyvar != NULL);
if(!Bounded(tyvar, methodInst.GetNumArgs()))
{
*pfHasCircularMethodConstraints = TRUE;
}
}
return;
}
#ifndef DACCESS_COMPILE
BOOL MethodDesc::SatisfiesMethodConstraints(TypeHandle thParent, BOOL fThrowIfNotSatisfied/* = FALSE*/)
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
INJECT_FAULT(COMPlusThrowOM());
}
CONTRACTL_END;
// nice: cache (positive?) result in (instantiated) methoddesc
// caveat: this would be unsafe for instantiated method desc living in generic,
// hence possibly shared classes (with varying class instantiations).
if (!HasMethodInstantiation())
return TRUE;
Instantiation methodInst = LoadMethodInstantiation();
Instantiation typicalInst = LoadTypicalMethodDefinition()->GetMethodInstantiation();
//NB: according to the constructor's signature, thParent should be the declaring type,
// but the code appears to admit derived types too.
SigTypeContext typeContext(this,thParent);
InstantiationContext instContext(&typeContext, NULL);
bool typicalInstMatchesMethodInst = true;
for (DWORD i = 0; i < methodInst.GetNumArgs(); i++)
{
if (typicalInst[i] != methodInst[i])
{
typicalInstMatchesMethodInst = false;
break;
}
}
for (DWORD i = 0; i < methodInst.GetNumArgs(); i++)
{
TypeHandle thArg = methodInst[i];
_ASSERTE(!thArg.IsNull());
TypeVarTypeDesc* tyvar = (TypeVarTypeDesc*) (typicalInst[i].AsTypeDesc());
_ASSERTE(tyvar != NULL);
_ASSERTE(TypeFromToken(tyvar->GetTypeOrMethodDef()) == mdtMethodDef);
tyvar->LoadConstraints(); //TODO: is this necessary for anything but the typical method?
// Pass in the InstatiationContext so constraints can be correctly evaluated
// if this is an instantiation where the type variable is in its open position
if (!tyvar->SatisfiesConstraints(&typeContext,thArg, typicalInstMatchesMethodInst ? &instContext : NULL))
{
if (fThrowIfNotSatisfied)
{
SString sParentName;
TypeString::AppendType(sParentName, thParent);
SString sMethodName(SString::Utf8, GetName());
SString sActualParamName;
TypeString::AppendType(sActualParamName, methodInst[i]);
SString sFormalParamName;
TypeString::AppendType(sFormalParamName, typicalInst[i]);
COMPlusThrow(kVerificationException,
IDS_EE_METHOD_CONSTRAINTS_VIOLATION,
sParentName.GetUnicode(),
sMethodName.GetUnicode(),
sActualParamName.GetUnicode(),
sFormalParamName.GetUnicode()
);
}
return FALSE;
}
}
return TRUE;
}
#endif // !DACCESS_COMPILE