// Value class boxing
if (srcTH.IsValueType() && !destTH.IsValueType())
{
- switch (srcTH.CanCastToNoGC(destTH))
+ switch (srcTH.CanCastToCached(destTH))
{
case TypeHandle::CanCast : return AssignBoxValueClassOrPrimitive;
case TypeHandle::CannotCast : return AssignWrongType;
// Value class unboxing.
if (!srcTH.IsValueType() && destTH.IsValueType())
{
- if (srcTH.CanCastToNoGC(destTH) == TypeHandle::CanCast)
+ if (srcTH.CanCastToCached(destTH) == TypeHandle::CanCast)
return AssignUnboxValueClass;
- else if (destTH.CanCastToNoGC(srcTH) == TypeHandle::CanCast) // V extends IV. Copying from IV to V, or Object to V.
+ else if (destTH.CanCastToCached(srcTH) == TypeHandle::CanCast) // V extends IV. Copying from IV to V, or Object to V.
return AssignUnboxValueClass;
else
return AssignDontKnow;
}
// dest Object extends src
- if (srcTH.CanCastToNoGC(destTH) == TypeHandle::CanCast)
+ if (srcTH.CanCastToCached(destTH) == TypeHandle::CanCast)
return AssignWillWork;
// src Object extends dest
- if (destTH.CanCastToNoGC(srcTH) == TypeHandle::CanCast)
+ if (destTH.CanCastToCached(srcTH) == TypeHandle::CanCast)
return AssignMustCast;
// class X extends/implements src and implements dest.
FC_GC_POLL();
return;
}
- else if (reliable) {
- FCThrowResVoid(kArrayTypeMismatchException, W("ArrayTypeMismatch_ConstrainedCopy"));
- }
HELPER_METHOD_FRAME_BEGIN_PROTECT(gc);
if (r == AssignDontKnow)
}
CONSISTENCY_CHECK(r != AssignDontKnow);
- if (r == AssignWrongType)
- COMPlusThrow(kArrayTypeMismatchException, W("ArrayTypeMismatch_CantAssignType"));
-
// If we were called from Array.ConstrainedCopy, ensure that the array copy
// is guaranteed to succeed.
- _ASSERTE(!reliable || r == AssignWillWork);
+ if (reliable && r != AssignWillWork)
+ COMPlusThrow(kArrayTypeMismatchException, W("ArrayTypeMismatch_ConstrainedCopy"));
+
+ if (r == AssignWrongType)
+ COMPlusThrow(kArrayTypeMismatchException, W("ArrayTypeMismatch_CantAssignType"));
if (m_iLength > 0)
{
else
if (!pTargetMT->IsValueType())
{
- if (ObjIsInstanceOfNoGC(OBJECTREFToObject(obj), thTarget) != TypeHandle::CanCast)
+ if (ObjIsInstanceOfCached(OBJECTREFToObject(obj), thTarget) != TypeHandle::CanCast)
{
// target->data is protected by the caller
HELPER_METHOD_FRAME_BEGIN_1(obj);
TypeHandle thFrom = TypeHandle(pMT);
TypeHandle thTarget = TypeHandle(pDE->m_md->GetMethodTable());
//<TODO> What about MaybeCast?</TODO>
- if (thFrom.CanCastToNoGC(thTarget) == TypeHandle::CannotCast)
+ if (thFrom.CanCastToCached(thTarget) == TypeHandle::CannotCast)
{
COMPlusThrow(kArgumentException, W("Argument_CORDBBadMethod"));
}
assembly.cpp
baseassemblyspec.cpp
binder.cpp
+ castcache.cpp
callcounter.cpp
ceeload.cpp
class.cpp
baseassemblyspec.h
baseassemblyspec.inl
binder.h
+ castcache.h
ceeload.h
ceeload.inl
class.h
; PERF TODO: consider prefetching the entire interface map into the cache
-; For all bizarre castes this quickly fails and falls back onto the JITutil_IsInstanceOfAny
+; For all bizarre castes this quickly fails and falls back onto the JITutil_IsInstanceOfInterface
; helper, this means that all failure cases take the slow path as well.
;
; This can trash r10/r11
EXTERN g_pObjectClass:qword
extern ArrayStoreCheck:proc
-extern ObjIsInstanceOfNoGC:proc
+extern ObjIsInstanceOfCached:proc
; TODO: put definition for this in asmconstants.h
CanCast equ 1
cmp r9, [g_pObjectClass]
je DoWrite
- jmp JIT_Stelem_Ref__ObjIsInstanceOfNoGC_Helper
+ jmp JIT_Stelem_Ref__ObjIsInstanceOfCached_Helper
ThrowNullReferenceException:
mov rcx, CORINFO_NullReferenceException_ASM
jmp JIT_InternalThrow
LEAF_END JIT_Stelem_Ref, _TEXT
-NESTED_ENTRY JIT_Stelem_Ref__ObjIsInstanceOfNoGC_Helper, _TEXT
+NESTED_ENTRY JIT_Stelem_Ref__ObjIsInstanceOfCached_Helper, _TEXT
alloc_stack MIN_SIZE
save_reg_postrsp rcx, MIN_SIZE + 8h
save_reg_postrsp rdx, MIN_SIZE + 10h
mov rdx, r9
mov rcx, r8
- ; TypeHandle::CastResult ObjIsInstanceOfNoGC(Object *pElement, TypeHandle toTypeHnd)
- call ObjIsInstanceOfNoGC
+ ; TypeHandle::CastResult ObjIsInstanceOfCached(Object *pElement, TypeHandle toTypeHnd)
+ call ObjIsInstanceOfCached
mov rcx, [rsp + MIN_SIZE + 8h]
mov rdx, [rsp + MIN_SIZE + 10h]
NeedCheck:
add rsp, MIN_SIZE
jmp JIT_Stelem_Ref__ArrayStoreCheck_Helper
-NESTED_END JIT_Stelem_Ref__ObjIsInstanceOfNoGC_Helper, _TEXT
+NESTED_END JIT_Stelem_Ref__ObjIsInstanceOfCached_Helper, _TEXT
; Need to save r8 to provide a stack address for the Object*
NESTED_ENTRY JIT_Stelem_Ref__ArrayStoreCheck_Helper, _TEXT
cmp rcx, [r11]
je LOCAL_LABEL(DoWrite)
- jmp C_FUNC(JIT_Stelem_Ref__ObjIsInstanceOfNoGC_Helper)
+ jmp C_FUNC(JIT_Stelem_Ref__ObjIsInstanceOfCached_Helper)
LOCAL_LABEL(ThrowNullReferenceException):
mov rdi, CORINFO_NullReferenceException_ASM
jmp C_FUNC(JIT_InternalThrow)
LEAF_END JIT_Stelem_Ref, _TEXT
-LEAF_ENTRY JIT_Stelem_Ref__ObjIsInstanceOfNoGC_Helper, _TEXT
+LEAF_ENTRY JIT_Stelem_Ref__ObjIsInstanceOfCached_Helper, _TEXT
push_nonvol_reg rbp
mov rbp, rsp
set_cfa_register rbp, 16
mov rsi, rcx
mov rdi, rdx
- // TypeHandle::CastResult ObjIsInstanceOfNoGC(Object *pElement, TypeHandle toTypeHnd)
- call C_FUNC(ObjIsInstanceOfNoGC)
+ // TypeHandle::CastResult ObjIsInstanceOfCached(Object *pElement, TypeHandle toTypeHnd)
+ call C_FUNC(ObjIsInstanceOfCached)
mov rdi, [rbp - 0x08]
mov rsi, [rbp - 0x10]
LOCAL_LABEL(NeedCheck):
jmp C_FUNC(JIT_Stelem_Ref__ArrayStoreCheck_Helper)
-LEAF_END JIT_Stelem_Ref__ObjIsInstanceOfNoGC_Helper, _TEXT
+LEAF_END JIT_Stelem_Ref__ObjIsInstanceOfCached_Helper, _TEXT
// Need to save reg to provide a stack address for the Object*
LEAF_ENTRY JIT_Stelem_Ref__ArrayStoreCheck_Helper, _TEXT
CHECK_STACK_ALIGNMENT
// allow in case val can be casted to array element type
- // call ObjIsInstanceOfNoGC(val, array->GetArrayElementTypeHandle())
+ // call ObjIsInstanceOfCached(val, array->GetArrayElementTypeHandle())
mov r1, r12 // array->GetArrayElementTypeHandle()
mov r0, r2
- bl C_FUNC(ObjIsInstanceOfNoGC)
+ bl C_FUNC(ObjIsInstanceOfCached)
cmp r0, TypeHandle_CanCast
beq LOCAL_LABEL(DoWrite) // ObjIsInstance returned TypeHandle::CanCast
IMPORT PreStubWorker
IMPORT PreStubGetMethodDescForCompactEntryPoint
IMPORT NDirectImportWorker
- IMPORT ObjIsInstanceOfNoGC
+ IMPORT ObjIsInstanceOfCached
IMPORT ArrayStoreCheck
IMPORT VSD_ResolveWorker
IMPORT $g_pObjectClass
CHECK_STACK_ALIGNMENT
; allow in case val can be casted to array element type
- ; call ObjIsInstanceOfNoGC(val, array->GetArrayElementTypeHandle())
+ ; call ObjIsInstanceOfCached(val, array->GetArrayElementTypeHandle())
mov r1, r12 ; array->GetArrayElementTypeHandle()
mov r0, r2
- bl ObjIsInstanceOfNoGC
+ bl ObjIsInstanceOfCached
cmp r0, TypeHandle_CanCast
beq DoWrite ; ObjIsInstance returned TypeHandle::CanCast
PROLOG_SAVE_REG x2, 32
// allow in case val can be casted to array element type
- // call ObjIsInstanceOfNoGC(val, array->GetArrayElementTypeHandle())
+ // call ObjIsInstanceOfCached(val, array->GetArrayElementTypeHandle())
mov x1, x12 // array->GetArrayElementTypeHandle()
mov x0, x2
- bl C_FUNC(ObjIsInstanceOfNoGC)
+ bl C_FUNC(ObjIsInstanceOfCached)
cmp x0, TypeHandle_CanCast
beq LOCAL_LABEL(DoWrite) // ObjIsInstance returned TypeHandle::CanCast
IMPORT DynamicHelperWorker
#endif
- IMPORT ObjIsInstanceOfNoGC
+ IMPORT ObjIsInstanceOfCached
IMPORT ArrayStoreCheck
SETALIAS g_pObjectClass, ?g_pObjectClass@@3PEAVMethodTable@@EA
IMPORT $g_pObjectClass
str x2, [sp, #32]
; allow in case val can be casted to array element type
- ; call ObjIsInstanceOfNoGC(val, array->GetArrayElementTypeHandle())
+ ; call ObjIsInstanceOfCached(val, array->GetArrayElementTypeHandle())
mov x1, x12 ; array->GetArrayElementTypeHandle()
mov x0, x2
- bl ObjIsInstanceOfNoGC
+ bl ObjIsInstanceOfCached
cmp x0, TypeHandle_CanCast
beq DoWrite ; ObjIsInstance returned TypeHandle::CanCast
{
LIMITED_METHOD_CONTRACT;
PRECONDITION(pInterfaceMT->IsInterface());
+ PRECONDITION(pInterfaceMT->HasInstantiation());
// Is target interface Anything<T> in mscorlib?
if (!pInterfaceMT->HasInstantiation() || !pInterfaceMT->GetModule()->IsSystem())
rid == MscorlibBinder::GetExistingClass(CLASS__IREADONLYLISTGENERIC)->GetTypeDefRid());
}
-//---------------------------------------------------------------------
-// Check if arrays supports certain interfaces that don't appear in the base interface
-// list. It does not check the base interfaces themselves - you must do that
-// separately.
-//---------------------------------------------------------------------
-BOOL ArraySupportsBizarreInterface(ArrayTypeDesc *pArrayTypeDesc, MethodTable *pInterfaceMT)
-{
- CONTRACTL
- {
- THROWS;
- GC_TRIGGERS;
- INJECT_FAULT(COMPlusThrowOM(););
-
- PRECONDITION(pInterfaceMT->IsInterface());
- PRECONDITION(pArrayTypeDesc->IsArray());
- }
- CONTRACTL_END
-
-#ifdef _DEBUG
- MethodTable *pArrayMT = pArrayTypeDesc->GetMethodTable();
- _ASSERTE(pArrayMT->IsArray());
- _ASSERTE(pArrayMT->IsRestored());
-#endif
-
- // IList<T> & IReadOnlyList<T> only supported for SZ_ARRAYS
- if (pArrayTypeDesc->GetInternalCorElementType() != ELEMENT_TYPE_SZARRAY)
- return FALSE;
-
- ClassLoader::EnsureLoaded(pInterfaceMT, CLASS_DEPENDENCIES_LOADED);
-
- if (!IsImplicitInterfaceOfSZArray(pInterfaceMT))
- return FALSE;
-
- return TypeDesc::CanCastParam(pArrayTypeDesc->GetTypeParam(), pInterfaceMT->GetInstantiation()[0], NULL);
-}
-
//----------------------------------------------------------------------------------
// Calls to (IList<T>)(array).Meth are actually implemented by SZArrayHelper.Meth<T>
// This workaround exists for two reasons:
BOOL IsImplicitInterfaceOfSZArray(MethodTable *pIntfMT);
-BOOL ArraySupportsBizarreInterface(ArrayTypeDesc *pArrayTypeDesc, MethodTable *pInterfaceMT);
MethodDesc* GetActualImplementationForArrayGenericIListOrIReadOnlyListMethod(MethodDesc *pItfcMeth, TypeHandle theT);
CorElementType GetNormalizedIntegralArrayElementType(CorElementType elementType);
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+//
+// File: castcache.cpp
+//
+
+#include "common.h"
+#include "castcache.h"
+
+#if !defined(DACCESS_COMPILE) && !defined(CROSSGEN_COMPILE)
+
+OBJECTHANDLE CastCache::s_cache = NULL;
+DWORD CastCache::s_lastFlushSize = INITIAL_CACHE_SIZE;
+
+BASEARRAYREF CastCache::CreateCastCache(DWORD size)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ }
+ CONTRACTL_END;
+
+ // size must be positive
+ _ASSERTE(size > 0);
+ // size must be a power of two
+ _ASSERTE((size & (size - 1)) == 0);
+
+ BASEARRAYREF table = NULL;
+
+ // if we get an OOM here, we try a smaller size
+ EX_TRY
+ {
+ FAULT_NOT_FATAL();
+ table = (BASEARRAYREF)AllocatePrimitiveArray(CorElementType::ELEMENT_TYPE_I4, (size + 1) * sizeof(CastCacheEntry) / sizeof(INT32));
+ }
+ EX_CATCH
+ {
+ }
+ EX_END_CATCH(RethrowCorruptingExceptions)
+
+ if (!table)
+ {
+ size = INITIAL_CACHE_SIZE;
+ // if we get an OOM again we return NULL
+ EX_TRY
+ {
+ FAULT_NOT_FATAL();
+ table = (BASEARRAYREF)AllocatePrimitiveArray(CorElementType::ELEMENT_TYPE_I4, (size + 1) * sizeof(CastCacheEntry) / sizeof(INT32));
+ }
+ EX_CATCH
+ {
+ }
+ EX_END_CATCH(RethrowCorruptingExceptions)
+
+ if (!table)
+ {
+ // OK, no cache then
+ return NULL;
+ }
+ }
+
+ TableMask(table) = size - 1;
+
+ // Fibonacci hash reduces the value into desired range by shifting right by the number of leading zeroes in 'size-1'
+ DWORD bitCnt;
+#if BIT64
+ BitScanReverse64(&bitCnt, size - 1);
+ HashShift(table) = (BYTE)(63 - bitCnt);
+#else
+ BitScanReverse(&bitCnt, size - 1);
+ HashShift(table) = (BYTE)(31 - bitCnt);
+#endif
+
+ return table;
+}
+
+BOOL CastCache::MaybeReplaceCacheWithLarger(DWORD size)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ }
+ CONTRACTL_END;
+
+ BASEARRAYREF newTable = CreateCastCache(size);
+ if (!newTable)
+ {
+ return FALSE;
+ }
+
+ StoreObjectInHandle(s_cache, newTable);
+ return TRUE;
+}
+
+void CastCache::FlushCurrentCache()
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_COOPERATIVE;
+ }
+ CONTRACTL_END;
+
+ BASEARRAYREF currentTableRef = (BASEARRAYREF)ObjectFromHandle(s_cache);
+ s_lastFlushSize = !currentTableRef ? INITIAL_CACHE_SIZE : CacheElementCount(currentTableRef);
+
+ StoreObjectInHandle(s_cache, NULL);
+}
+
+void CastCache::Initialize()
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ s_cache = CreateGlobalHandle(NULL);
+}
+
+TypeHandle::CastResult CastCache::TryGet(TADDR source, TADDR target)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_COOPERATIVE;
+ }
+ CONTRACTL_END;
+
+ BASEARRAYREF table = (BASEARRAYREF)ObjectFromHandle(s_cache);
+
+ // we use NULL as a sentinel for a rare case when a table could not be allocated
+ // because we avoid OOMs in conversions
+ // we could use 0-element table instead, but then we would have to check the size here.
+ if (!table)
+ {
+ return TypeHandle::MaybeCast;
+ }
+
+ DWORD index = KeyToBucket(table, source, target);
+ CastCacheEntry* pEntry = &Elements(table)[index];
+
+ for (DWORD i = 0; i < BUCKET_SIZE; i++)
+ {
+ // must read in this order: version -> entry parts -> version
+ // if version is odd or changes, the entry is inconsistent and thus ignored
+ DWORD version1 = VolatileLoad(&pEntry->version);
+ TADDR entrySource = pEntry->source;
+
+ if (entrySource == source)
+ {
+ TADDR entryTargetAndResult = VolatileLoad(&pEntry->targetAndResult);
+
+ // target never has its lower bit set.
+ // a matching entryTargetAndResult would have same bits, except for the lowest one, which is the result.
+ entryTargetAndResult ^= target;
+ if (entryTargetAndResult <= 1)
+ {
+ DWORD version2 = pEntry->version;
+ if (version2 != version1 || (version1 & 1))
+ {
+ // oh, so close, the entry is in inconsistent state.
+ // it is either changing or has changed while we were reading.
+ // treat it as a miss.
+ break;
+ }
+
+ return TypeHandle::CastResult(entryTargetAndResult);
+ }
+ }
+
+ if (version1 == 0)
+ {
+ // the rest of the bucket is unclaimed, no point to search further
+ break;
+ }
+
+ // quadratic reprobe
+ index += i;
+ pEntry = &Elements(table)[index & TableMask(table)];
+ }
+
+ return TypeHandle::MaybeCast;
+}
+
+void CastCache::TrySet(TADDR source, TADDR target, BOOL result)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ }
+ CONTRACTL_END;
+
+ DWORD bucket;
+ BASEARRAYREF table;
+
+ do
+ {
+ table = (BASEARRAYREF)ObjectFromHandle(s_cache);
+ if (!table)
+ {
+ // we did not allocate a table or flushed it, try replacing, but do not continue looping.
+ MaybeReplaceCacheWithLarger(s_lastFlushSize);
+ return;
+ }
+
+ bucket = KeyToBucket(table, source, target);
+ DWORD index = bucket;
+ CastCacheEntry* pEntry = &Elements(table)[index];
+
+ for (DWORD i = 0; i < BUCKET_SIZE; i++)
+ {
+ // claim the entry if unused or is more distant than us from its origin.
+ // Note - someone familiar with Robin Hood hashing will notice that
+ // we do the opposite - we are "robbing the poor".
+ // Robin Hood strategy improves average lookup in a lossles dictionary by reducing
+ // outliers via giving preference to more distant entries.
+ // What we have here is a lossy cache with outliers bounded by the bucket size.
+ // We improve average lookup by giving preference to the "richer" entries.
+ // If we used Robin Hood strategy we could eventually end up with all
+ // entries in the table being maximally "poor".
+ DWORD version = pEntry->version;
+ if (version == 0 || (version >> VERSION_NUM_SIZE) > i)
+ {
+ DWORD newVersion = (i << VERSION_NUM_SIZE) + (version & VERSION_NUM_MASK) + 1;
+ DWORD versionOrig = InterlockedCompareExchangeT(&pEntry->version, newVersion, version);
+ if (versionOrig == version)
+ {
+ pEntry->SetEntry(source, target, result);
+
+ // entry is in inconsistent state and cannot be read or written to until we
+ // update the version, which is the last thing we do here
+ VolatileStore(&pEntry->version, newVersion + 1);
+ return;
+ }
+ // someone snatched the entry. try the next one in the bucket.
+ }
+
+ if (pEntry->Source() == source && pEntry->Target() == target)
+ {
+ // looks like we already have an entry for this.
+ // duplicate entries are harmless, but a bit of a waste.
+ return;
+ }
+
+ // quadratic reprobe
+ index += i;
+ pEntry = &Elements(table)[index & TableMask(table)];
+ }
+
+ // bucket is full.
+ } while (TryGrow(table));
+
+ // reread table after TryGrow.
+ table = (BASEARRAYREF)ObjectFromHandle(s_cache);
+ if (!table)
+ {
+ // we did not allocate a table.
+ return;
+ }
+
+ // pick a victim somewhat randomly within a bucket
+ // NB: ++ is not interlocked. We are ok if we lose counts here. It is just a number that changes.
+ DWORD victimDistance = VictimCounter(table)++ & (BUCKET_SIZE - 1);
+ // position the victim in a quadratic reprobe bucket
+ DWORD victim = (victimDistance * victimDistance + victimDistance) / 2;
+
+ {
+ CastCacheEntry* pEntry = &Elements(table)[(bucket + victim) & TableMask(table)];
+
+ DWORD version = pEntry->version;
+ if ((version & VERSION_NUM_MASK) >= (VERSION_NUM_MASK - 2))
+ {
+ // It is unlikely for a reader to sit between versions while exactly 2^VERSION_NUM_SIZE updates happens.
+ // Anyways, to not bother about the possibility, lets get a new cache. It will not happen often, if ever.
+ FlushCurrentCache();
+ return;
+ }
+
+ DWORD newVersion = (victimDistance << VERSION_NUM_SIZE) + (version & VERSION_NUM_MASK) + 1;
+ DWORD versionOrig = InterlockedCompareExchangeT(&pEntry->version, newVersion, version);
+
+ if (versionOrig == version)
+ {
+ pEntry->SetEntry(source, target, result);
+ VolatileStore(&pEntry->version, newVersion + 1);
+ }
+ }
+}
+
+#endif // !DACCESS_COMPILE && !CROSSGEN_COMPILE
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+//
+// File: castcache.h
+//
+
+#ifndef _CAST_CACHE_H
+#define _CAST_CACHE_H
+
+#include "util.hpp"
+
+//
+// A very lightweight cache that maps {source, target} -> result, where result is
+// a boolean value indicating that the type of the source can cast to the target type or
+// definitely cannot.
+//
+// In the terminology of ECMA335 the relationship is called "compatible-with".
+// This is the relation used by castclass and isinst (III.4.3).
+//
+// We generically allow either MethodTable* or TypeHandle as source/target. Either value
+// uniquely maps to a single type with no possibility of confusion. Besides for most types
+// TypeHandle a MethodTable* are the same value anyways.
+//
+// One thing to consider is that cast analysis is relatively fast, which demands that the cache is fast too.
+// On the other hand, we do not need to be 100% accurate about a presence of an entry in a cache,
+// since everything can be re-computed relatively quickly.
+// We still hope to have a good hit rate, but can tolerate items pushed/flushed from the cache.
+//
+// The overal design of the cache is an open-addressing hash table with quadratic probing
+// strategy and a limited bucket size.
+// In a case of inserting we -
+// 1) use an empty entry within the bucket path or preempt an entry with a longer distance from it origin.
+// 2) pick a random victim entry within the bucket and replace it with a new entry.
+// That is basically our expiration policy. We want to keep things simple.
+//
+// The cache permits fully concurrent writes and stores. We use versioned entries to detect incomplete states and
+// tearing, which happens temporarily during updating. Entries in an inconsistent state are ignored by readers and writers.
+// As a result TryGet is Wait-Free - no locking or spinning.
+// TryAdd is mostly Wait-Free (may try allocating a new table), but is more complex than TryGet.
+//
+// The assumption that same source and target keep the same relationship could be
+// broken if the types involved are unloaded and their handles are reused. (ABA problem).
+// To counter that possibility we simply flush the whole cache on assembly unloads.
+//
+// Whenever we need to replace or resize the table, we simply allocate a new one and atomically
+// update the static handle. The old table may be still in use, but will eventually be collected by GC.
+//
+class CastCache
+{
+#if !defined(DACCESS_COMPILE) && !defined(CROSSGEN_COMPILE)
+
+ static const int VERSION_NUM_SIZE = 29;
+ static const int VERSION_NUM_MASK = (1 << VERSION_NUM_SIZE) - 1;
+
+ struct CastCacheEntry
+ {
+ // version has the following structure:
+ // [ distance:3bit | versionNum:29bit ]
+ //
+ // distance is how many iterations is the entry from it ideal position.
+ // we use that for preemption.
+ //
+ // versionNum is a monotonicaly increasing numerical tag.
+ // Writer "claims" entry by atomically incrementing the tag. Thus odd number indicates an entry in progress.
+ // Upon completion of adding an entry the tag is incremented again making it even. Even number indicates a complete entry.
+ //
+ // Readers will read the version twice before and after retrieving the entry.
+ // To have a usable entry both reads must yield the same even version.
+ //
+ DWORD version;
+ TADDR source;
+ // pointers have unused lower bits due to alignment, we use one for the result
+ TADDR targetAndResult;
+
+ FORCEINLINE TADDR Source()
+ {
+ return source;
+ }
+
+ FORCEINLINE TADDR Target()
+ {
+ return targetAndResult & ~(TADDR)1;
+ }
+
+ FORCEINLINE BOOL Result()
+ {
+ return targetAndResult & 1;
+ };
+
+ FORCEINLINE void SetEntry(TADDR source, TADDR target, BOOL result)
+ {
+ this->source = source;
+ this->targetAndResult = target | (result & 1);
+ }
+ };
+
+public:
+
+ FORCEINLINE static void TryAddToCache(TypeHandle source, TypeHandle target, BOOL result)
+ {
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ }
+ CONTRACTL_END;
+
+ // fully loaded types cannot be "undone" and thus castability can be cached.
+ // do not cache if any of the types is not fully loaded.
+ if (!source.IsFullyLoaded() || !target.IsFullyLoaded())
+ return;
+
+ // we should not be caching T --> Nullable<T>. result is contextual.
+ // unsubsttituted generic T is ok though. there is an agreement on that.
+ _ASSERTE(source.IsTypeDesc() || !Nullable::IsNullableForType(target, source.AsMethodTable()));
+
+ TryAddToCache(source.AsTAddr(), target.AsTAddr(), result);
+ }
+
+ FORCEINLINE static void TryAddToCache(MethodTable* pSourceMT, TypeHandle target, BOOL result)
+ {
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ }
+ CONTRACTL_END;
+
+ // fully loaded types cannot be "undone" and thus castability can be cached.
+ // do not cache if any of the types is not fully loaded.
+ if (!pSourceMT->IsFullyLoaded() || !target.IsFullyLoaded())
+ return;
+
+ // we should not be caching T --> Nullable<T>. result is contextual.
+ _ASSERTE(!Nullable::IsNullableForType(target, pSourceMT));
+
+ TryAddToCache((TADDR)pSourceMT, target.AsTAddr(), result);
+ }
+
+ FORCEINLINE static TypeHandle::CastResult TryGetFromCache(TypeHandle source, TypeHandle target)
+ {
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_COOPERATIVE;
+ }
+ CONTRACTL_END;
+
+ return TryGetFromCache(source.AsTAddr(), target.AsTAddr());
+ }
+
+ FORCEINLINE static TypeHandle::CastResult TryGetFromCache(MethodTable* pSourceMT, TypeHandle target)
+ {
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_COOPERATIVE;
+ }
+ CONTRACTL_END;
+
+ return TryGetFromCache((TADDR)pSourceMT, target.AsTAddr());
+ }
+
+ static void FlushCurrentCache();
+ static void Initialize();
+
+private:
+
+// The cache size is driven by demand and generally is fairly small. (casts are repetitive)
+// Even conversion-churning tests such as Linq.Expressions will not need > 4096
+// When we reach the limit, the new entries start replacing the old ones somewhat randomly.
+// Considering that typically the cache size is small and that hit rates are high with good locality,
+// just keeping the cache around seems a simple and viable strategy.
+//
+// Additional behaviors that could be considered, if there are scenarios that could be improved:
+// - flush the cache based on some heuristics
+// - shrink the cache based on some heuristics
+//
+#if DEBUG
+ static const DWORD INITIAL_CACHE_SIZE = 8; // MUST BE A POWER OF TWO
+ static const DWORD MAXIMUM_CACHE_SIZE = 512; // make this lower than release to make it easier to reach this in tests.
+#else
+ static const DWORD INITIAL_CACHE_SIZE = 128; // MUST BE A POWER OF TWO
+ static const DWORD MAXIMUM_CACHE_SIZE = 4096; // 4096 * sizeof(CastCacheEntry) is 98304 bytes on 64bit. We will rarely need this much though.
+#endif
+
+// Lower bucket size will cause the table to resize earlier
+// Higher bucket size will increase upper bound cost of Get
+//
+// In a cold scenario and 64byte cache line:
+// 1 cache miss for 1 probe,
+// 2 sequential misses for 3 probes,
+// then a miss can be assumed for every additional probe.
+// We pick 8 as the probe limit (hoping for 4 probes on average), but the number can be refined further.
+ static const DWORD BUCKET_SIZE = 8;
+
+ static OBJECTHANDLE s_cache;
+ static DWORD s_lastFlushSize;
+
+ FORCEINLINE static TypeHandle::CastResult TryGetFromCache(TADDR source, TADDR target)
+ {
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_COOPERATIVE;
+ }
+ CONTRACTL_END;
+
+ if (source == target)
+ {
+ return TypeHandle::CanCast;
+ }
+
+ return TryGet(source, target);
+ }
+
+ FORCEINLINE static void TryAddToCache(TADDR source, TADDR target, BOOL result)
+ {
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ }
+ CONTRACTL_END;
+
+ if (source == target)
+ return;
+
+ TrySet(source, target, result);
+ }
+
+ FORCEINLINE static bool TryGrow(BASEARRAYREF table)
+ {
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ }
+ CONTRACTL_END;
+
+ DWORD newSize = CacheElementCount(table) * 2;
+ if (newSize <= MAXIMUM_CACHE_SIZE)
+ {
+ return MaybeReplaceCacheWithLarger(newSize);
+ }
+
+ return false;
+ }
+
+ FORCEINLINE static DWORD KeyToBucket(BASEARRAYREF table, TADDR source, TADDR target)
+ {
+ // upper bits of addresses do not vary much, so to reduce loss due to cancelling out,
+ // we do `rotl(source, <half-size>) ^ target` for mixing inputs.
+ // then we use fibonacci hashing to reduce the value to desired size.
+
+#if BIT64
+ TADDR hash = (((ULONGLONG)source << 32) | ((ULONGLONG)source >> 32)) ^ target;
+ return (DWORD)((hash * 11400714819323198485llu) >> HashShift(table));
+#else
+ TADDR hash = _rotl(source, 16) ^ target;
+ return (DWORD)((hash * 2654435769ul) >> HashShift(table));
+#endif
+ }
+
+ FORCEINLINE static byte* AuxData(BASEARRAYREF table)
+ {
+ LIMITED_METHOD_CONTRACT;
+
+ // element 0 is used for embedded aux data
+ return (byte*)OBJECTREFToObject(table) + ARRAYBASE_SIZE;
+ }
+
+ FORCEINLINE static CastCacheEntry* Elements(BASEARRAYREF table)
+ {
+ LIMITED_METHOD_CONTRACT;
+ // element 0 is used for embedded aux data, skip it
+ return (CastCacheEntry*)AuxData(table) + 1;
+ }
+
+ // TableMask is "size - 1"
+ // we need that more often that we need size
+ FORCEINLINE static DWORD& TableMask(BASEARRAYREF table)
+ {
+ LIMITED_METHOD_CONTRACT;
+ return *(DWORD*)AuxData(table);
+ }
+
+ FORCEINLINE static BYTE& HashShift(BASEARRAYREF table)
+ {
+ LIMITED_METHOD_CONTRACT;
+ return *((BYTE*)AuxData(table) + sizeof(DWORD));
+ }
+
+ FORCEINLINE static BYTE& VictimCounter(BASEARRAYREF table)
+ {
+ LIMITED_METHOD_CONTRACT;
+ return *((BYTE*)AuxData(table) + sizeof(DWORD) + 1);
+ }
+
+ FORCEINLINE static DWORD CacheElementCount(BASEARRAYREF table)
+ {
+ LIMITED_METHOD_CONTRACT;
+ return TableMask(table) + 1;
+ }
+
+ static BASEARRAYREF CreateCastCache(DWORD size);
+ static BOOL MaybeReplaceCacheWithLarger(DWORD size);
+ static TypeHandle::CastResult TryGet(TADDR source, TADDR target);
+ static void TrySet(TADDR source, TADDR target, BOOL result);
+
+#else // !DACCESS_COMPILE && !CROSSGEN_COMPILE
+public:
+ FORCEINLINE static void TryAddToCache(TypeHandle source, TypeHandle target, BOOL result)
+ {
+ }
+
+ FORCEINLINE static void TryAddToCache(MethodTable* pSourceMT, TypeHandle target, BOOL result)
+ {
+ }
+
+ FORCEINLINE static TypeHandle::CastResult TryGetFromCache(TypeHandle source, TypeHandle target)
+ {
+ return TypeHandle::MaybeCast;
+ }
+
+ static void CastCache::Initialize()
+ {
+ }
+
+#endif // !DACCESS_COMPILE && !CROSSGEN_COMPILE
+};
+
+#endif
#include "threadsuspend.h"
#include "disassembler.h"
#include "jithost.h"
+#include "castcache.h"
#ifndef FEATURE_PAL
#include "dwreport.h"
// Now we really have fully initialized the garbage collector
SetGarbageCollectorFullyInitialized();
+ // This will allocate a handle, so do this after GC is initialized.
+ CastCache::Initialize();
+
#ifdef DEBUGGING_SUPPORTED
// Make a call to publish the DefaultDomain for the debugger
// This should be done before assemblies/modules are loaded into it (i.e. SystemDomain::Init)
../assemblyspec.cpp
../baseassemblyspec.cpp
../binder.cpp
+ ../castcache.cpp
../ceeload.cpp
../ceemain.cpp
../class.cpp
_JIT_PatchedCodeEnd@0 endp
; This is the ASM portion of JIT_IsInstanceOfInterface. For all the bizarre cases, it quickly
-; fails and falls back on the JITutil_IsInstanceOfAny helper. So all failure cases take
+; fails and falls back on the JITutil_IsInstanceOfInterface helper. So all failure cases take
; the slow path, too.
;
; ARGUMENT_REG1 = array or interface to check for.
push EDX // element type handle
push EAX // object
- call ObjIsInstanceOfNoGC
+ call ObjIsInstanceOfCached
pop ECX // caller-restore ECX and EDX
pop EDX
X86EmitOp(0x3b, kEAX, kArrayMTReg, MethodTable::GetOffsetOfArrayElementTypeHandle() AMD64_ARG(k64BitOp));
X86EmitCondJump(CheckPassed, X86CondCode::kJZ); // Assigning to array of object is OK
- // Try to call the fast helper first ( ObjIsInstanceOfNoGC ).
+ // Try to call the fast helper first ( ObjIsInstanceOfCached ).
// If that fails we will fall back to calling the slow helper ( ArrayStoreCheck ) that erects a frame.
// See also JitInterfaceX86::JIT_Stelem_Ref
// it in the fast path anyway. the reason for that is that it makes
// the cleanup code much easier ( we have only 1 place to cleanup the stack and
// restore it to the original state )
- X86EmitCall(NewExternalCodeLabel((LPVOID)ObjIsInstanceOfNoGC), 0);
- X86EmitCmpRegImm32( kEAX, TypeHandle::CanCast); // CMP EAX, CanCast ; if ObjIsInstanceOfNoGC returns CanCast, we will go the fast path
+ X86EmitCall(NewExternalCodeLabel((LPVOID)ObjIsInstanceOfCached), 0);
+ X86EmitCmpRegImm32( kEAX, TypeHandle::CanCast); // CMP EAX, CanCast ; if ObjIsInstanceOfCached returns CanCast, we will go the fast path
CodeLabel * Cleanup = NewCodeLabel();
X86EmitCondJump(Cleanup, X86CondCode::kJZ);
#endif // HAVE_GCCOVER
#include "runtimehandles.h"
+#include "castcache.h"
//========================================================================
//
//
//========================================================================
-// pObject MUST be an instance of an array.
-TypeHandle::CastResult ArrayIsInstanceOfNoGC(Object *pObject, TypeHandle toTypeHnd)
+TypeHandle::CastResult STDCALL ObjIsInstanceOfCached(Object *pObject, TypeHandle toTypeHnd)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
MODE_COOPERATIVE;
PRECONDITION(CheckPointer(pObject));
- PRECONDITION(pObject->GetMethodTable()->IsArray());
- PRECONDITION(toTypeHnd.IsArray());
} CONTRACTL_END;
- ArrayBase *pArray = (ArrayBase*) pObject;
- ArrayTypeDesc *toArrayType = toTypeHnd.AsArray();
-
- // GetRank touches EEClass. Try to avoid it for SZArrays.
- if (toArrayType->GetInternalCorElementType() == ELEMENT_TYPE_SZARRAY)
- {
- if (pArray->GetMethodTable()->IsMultiDimArray())
- return TypeHandle::CannotCast;
- }
- else
- {
- if (pArray->GetRank() != toArrayType->GetRank())
- return TypeHandle::CannotCast;
- }
- _ASSERTE(pArray->GetRank() == toArrayType->GetRank());
-
- // ArrayBase::GetTypeHandle consults the loader tables to find the
- // exact type handle for an array object. This can be disproportionately slow - but after
- // all, why should we need to go looking up hash tables just to do a cast test?
- //
- // Thus we can always special-case the casting logic to avoid fetching this
- // exact type handle. Here we have only done so for one
- // particular case, i.e. when we are trying to cast to an array type where
- // there is an exact match between the rank, kind and element type of the two
- // array types. This happens when, for example, assigning an int32[] into an int32[][].
- //
-
- TypeHandle elementTypeHandle = pArray->GetArrayElementTypeHandle();
- TypeHandle toElementTypeHandle = toArrayType->GetArrayElementTypeHandle();
-
- if (elementTypeHandle == toElementTypeHandle)
- return TypeHandle::CanCast;
-
- // By this point we know that toArrayType->GetInternalCorElementType matches the element type of the Array object
- // so we can use a faster constructor to create the TypeDesc. (It so happens that ArrayTypeDescs derives from ParamTypeDesc
- // and can be created as identical in a slightly faster way with the following set of parameters.)
- ParamTypeDesc arrayType(toArrayType->GetInternalCorElementType(), pArray->GetMethodTable(), elementTypeHandle);
- return arrayType.CanCastToNoGC(toTypeHnd);
-}
-
-// pObject MUST be an instance of an array.
-TypeHandle::CastResult ArrayObjSupportsBizarreInterfaceNoGC(Object *pObject, MethodTable * pInterfaceMT)
-{
- CONTRACTL {
- NOTHROW;
- GC_NOTRIGGER;
- MODE_COOPERATIVE;
- PRECONDITION(CheckPointer(pObject));
- PRECONDITION(pObject->GetMethodTable()->IsArray());
- PRECONDITION(pInterfaceMT->IsInterface());
- } CONTRACTL_END;
-
- ArrayBase *pArray = (ArrayBase*) pObject;
-
- // IList<T> & IReadOnlyList<T> only supported for SZ_ARRAYS
- if (pArray->GetMethodTable()->IsMultiDimArray())
- return TypeHandle::CannotCast;
-
- if (pInterfaceMT->GetLoadLevel() < CLASS_DEPENDENCIES_LOADED)
- {
- if (!pInterfaceMT->HasInstantiation())
- return TypeHandle::CannotCast;
- // The slow path will take care of restoring the interface
- return TypeHandle::MaybeCast;
- }
-
- if (!IsImplicitInterfaceOfSZArray(pInterfaceMT))
- return TypeHandle::CannotCast;
-
- return TypeDesc::CanCastParamNoGC(pArray->GetArrayElementTypeHandle(), pInterfaceMT->GetInstantiation()[0]);
-}
-
-TypeHandle::CastResult STDCALL ObjIsInstanceOfNoGC(Object *pObject, TypeHandle toTypeHnd)
-{
- CONTRACTL {
- NOTHROW;
- GC_NOTRIGGER;
- MODE_COOPERATIVE;
- PRECONDITION(CheckPointer(pObject));
- } CONTRACTL_END;
-
-
- MethodTable *pMT = pObject->GetMethodTable();
-
- // Quick exact match first
- if (TypeHandle(pMT) == toTypeHnd)
- return TypeHandle::CanCast;
-
- if ((toTypeHnd.IsInterface() && ( pMT->IsComObjectType() || pMT->IsICastable())))
- {
- return TypeHandle::MaybeCast;
- }
-
- if (pMT->IsArray())
- {
- if (toTypeHnd.IsArray())
- return ArrayIsInstanceOfNoGC(pObject, toTypeHnd);
-
- if (toTypeHnd.IsInterface())
- {
- MethodTable * pInterfaceMT = toTypeHnd.AsMethodTable();
- if (pInterfaceMT->HasInstantiation())
- return ArrayObjSupportsBizarreInterfaceNoGC(pObject, pInterfaceMT);
- return pMT->ImplementsInterface(pInterfaceMT) ? TypeHandle::CanCast : TypeHandle::CannotCast;
- }
-
- if (toTypeHnd == TypeHandle(g_pObjectClass) || toTypeHnd == TypeHandle(g_pArrayClass))
- return TypeHandle::CanCast;
-
- return TypeHandle::CannotCast;
- }
-
- if (toTypeHnd.IsTypeDesc())
- return TypeHandle::CannotCast;
-
- // allow an object of type T to be cast to Nullable<T> (they have the same representation)
- if (Nullable::IsNullableForTypeNoGC(toTypeHnd, pMT))
- return TypeHandle::CanCast;
-
- return pMT->CanCastToClassOrInterfaceNoGC(toTypeHnd.AsMethodTable());
+ MethodTable* pMT = pObject->GetMethodTable();
+ return CastCache::TryGetFromCache(pMT, toTypeHnd);
}
-BOOL ObjIsInstanceOf(Object *pObject, TypeHandle toTypeHnd, BOOL throwCastException)
+BOOL ObjIsInstanceOfCore(Object *pObject, TypeHandle toTypeHnd, BOOL throwCastException)
{
CONTRACTL {
THROWS;
} CONTRACTL_END;
BOOL fCast = FALSE;
+ MethodTable* pMT = pObject->GetMethodTable();
OBJECTREF obj = ObjectToOBJECTREF(pObject);
-
GCPROTECT_BEGIN(obj);
- TypeHandle fromTypeHnd = obj->GetTypeHandle();
-
- // If we are trying to cast a proxy we need to delegate to remoting
- // services which will determine whether the proxy and the type are compatible.
- // Start by doing a quick static cast check to see if the type information captured in
- // the metadata indicates that the cast is legal.
- if (fromTypeHnd.CanCastTo(toTypeHnd))
+ // we check nullable case first because it is not cacheable.
+ // object castability and type castability disagree on T --> Nullable<T>,
+ // so we can't put this in the cache
+ if (Nullable::IsNullableForType(toTypeHnd, pMT))
{
+ // allow an object of type T to be cast to Nullable<T> (they have the same representation)
fCast = TRUE;
}
- else
-#ifdef FEATURE_COMINTEROP
- // If we are casting a COM object from interface then we need to do a check to see
- // if it implements the interface.
- if (toTypeHnd.IsInterface() && fromTypeHnd.GetMethodTable()->IsComObjectType())
+ else if (pMT->IsArray())
{
- fCast = ComObject::SupportsInterface(obj, toTypeHnd.AsMethodTable());
+ if (toTypeHnd.IsArray())
+ {
+ fCast = pMT->ArrayIsInstanceOf(toTypeHnd, /* pVisited */ NULL);
+ }
+ else if (!toTypeHnd.IsTypeDesc())
+ {
+ MethodTable* toMT = toTypeHnd.AsMethodTable();
+ if (toMT->IsInterface() && toMT->HasInstantiation())
+ {
+ fCast = pMT->ArraySupportsBizarreInterface(toMT, /* pVisited */ NULL);
+ }
+ else
+ {
+ fCast = pMT->CanCastToClassOrInterface(toMT, /* pVisited */ NULL);
+ }
+ }
}
- else
-#endif // FEATURE_COMINTEROP
- if (Nullable::IsNullableForType(toTypeHnd, obj->GetMethodTable()))
+ else if (toTypeHnd.IsTypeDesc())
+ {
+ CastCache::TryAddToCache(pMT, toTypeHnd, FALSE);
+ fCast = FALSE;
+ }
+ else if (pMT->CanCastToClassOrInterface(toTypeHnd.AsMethodTable(), /* pVisited */ NULL))
{
- // allow an object of type T to be cast to Nullable<T> (they have the same representation)
fCast = TRUE;
}
-#ifdef FEATURE_ICASTABLE
- // If type implements ICastable interface we give it a chance to tell us if it can be casted
- // to a given type.
- else if (toTypeHnd.IsInterface() && fromTypeHnd.GetMethodTable()->IsICastable())
+ else
{
- // Make actuall call to ICastableHelpers.IsInstanceOfInterface(obj, interfaceTypeObj, out exception)
- OBJECTREF exception = NULL;
- GCPROTECT_BEGIN(exception);
-
- PREPARE_NONVIRTUAL_CALLSITE(METHOD__ICASTABLEHELPERS__ISINSTANCEOF);
+#ifdef FEATURE_COMINTEROP
+ // If we are casting a COM object from interface then we need to do a check to see
+ // if it implements the interface.
+ if (toTypeHnd.IsInterface() && pMT->IsComObjectType())
+ {
+ fCast = ComObject::SupportsInterface(obj, toTypeHnd.AsMethodTable());
+ }
+ else
+#endif // FEATURE_COMINTEROP
+#ifdef FEATURE_ICASTABLE
+ // If type implements ICastable interface we give it a chance to tell us if it can be casted
+ // to a given type.
+ if (toTypeHnd.IsInterface() && pMT->IsICastable())
+ {
+ // Make actuall call to ICastableHelpers.IsInstanceOfInterface(obj, interfaceTypeObj, out exception)
+ OBJECTREF exception = NULL;
+ GCPROTECT_BEGIN(exception);
- OBJECTREF managedType = toTypeHnd.GetManagedClassObject(); //GC triggers
+ PREPARE_NONVIRTUAL_CALLSITE(METHOD__ICASTABLEHELPERS__ISINSTANCEOF);
- DECLARE_ARGHOLDER_ARRAY(args, 3);
- args[ARGNUM_0] = OBJECTREF_TO_ARGHOLDER(obj);
- args[ARGNUM_1] = OBJECTREF_TO_ARGHOLDER(managedType);
- args[ARGNUM_2] = PTR_TO_ARGHOLDER(&exception);
+ OBJECTREF managedType = toTypeHnd.GetManagedClassObject(); //GC triggers
- CALL_MANAGED_METHOD(fCast, BOOL, args);
- INDEBUG(managedType = NULL); // managedType isn't protected during the call
+ DECLARE_ARGHOLDER_ARRAY(args, 3);
+ args[ARGNUM_0] = OBJECTREF_TO_ARGHOLDER(obj);
+ args[ARGNUM_1] = OBJECTREF_TO_ARGHOLDER(managedType);
+ args[ARGNUM_2] = PTR_TO_ARGHOLDER(&exception);
- if (!fCast && throwCastException && exception != NULL)
- {
- RealCOMPlusThrow(exception);
+ CALL_MANAGED_METHOD(fCast, BOOL, args);
+ INDEBUG(managedType = NULL); // managedType isn't protected during the call
+
+ if (!fCast && throwCastException && exception != NULL)
+ {
+ RealCOMPlusThrow(exception);
+ }
+ GCPROTECT_END(); //exception
}
- GCPROTECT_END(); //exception
+#endif // FEATURE_ICASTABLE
}
-#endif // FEATURE_ICASTABLE
- if (!fCast && throwCastException)
+ if (!fCast && throwCastException)
{
COMPlusThrowInvalidCastException(&obj, toTypeHnd);
}
return(fCast);
}
+BOOL ObjIsInstanceOf(Object* pObject, TypeHandle toTypeHnd, BOOL throwCastException)
+{
+ CONTRACTL{
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ PRECONDITION(CheckPointer(pObject));
+ } CONTRACTL_END;
+
+ MethodTable* pMT = pObject->GetMethodTable();
+ TypeHandle::CastResult result = CastCache::TryGetFromCache(pMT, toTypeHnd);
+
+ if (result == TypeHandle::CanCast ||
+ (result == TypeHandle::CannotCast && !throwCastException))
+ {
+ return (BOOL)result;
+ }
+
+ return ObjIsInstanceOfCore(pObject, toTypeHnd, throwCastException);
+}
+
//
// This optimization is intended for all non-framed casting helpers
//
return NULL;
}
- OBJECTREF refObj = ObjectToOBJECTREF(pObject);
- VALIDATEOBJECTREF(refObj);
-
- TypeHandle::CastResult result = refObj->GetMethodTable()->IsArray() ?
- ArrayIsInstanceOfNoGC(pObject, TypeHandle(type)) : TypeHandle::CannotCast;
-
+ TypeHandle th = TypeHandle(type);
+ TypeHandle::CastResult result = ObjIsInstanceOfCached(pObject, th);
if (result == TypeHandle::CanCast)
{
return pObject;
}
ENDFORBIDGC();
- Object* pRet = HCCALL2(JITutil_ChkCastAny, type, pObject);
+ Object* pRet = HCCALL2(JITutil_ChkCastAny_NoCacheLookup, type, pObject);
// Make sure that the fast helper have not lied
_ASSERTE(result != TypeHandle::CannotCast);
return pRet;
}
else
{
- switch (ArrayIsInstanceOfNoGC(pObject, TypeHandle(type))) {
+ TypeHandle th = TypeHandle(type);
+ TypeHandle::CastResult result = CastCache::TryGetFromCache(pMT, th);
+
+ switch (result) {
case TypeHandle::CanCast:
return pObject;
case TypeHandle::CannotCast:
}
ENDFORBIDGC();
- return HCCALL2(JITutil_IsInstanceOfAny, type, pObject);
+ return HCCALL2(JITutil_IsInstanceOfAny_NoCacheLookup, type, pObject);
}
HCIMPLEND
return NULL;
}
- switch (ObjIsInstanceOfNoGC(obj, TypeHandle(type))) {
+ TypeHandle th = TypeHandle(type);
+ switch (ObjIsInstanceOfCached(obj, th)) {
case TypeHandle::CanCast:
return obj;
case TypeHandle::CannotCast:
}
ENDFORBIDGC();
- return HCCALL2(JITutil_IsInstanceOfAny, type, obj);
+ return HCCALL2(JITutil_IsInstanceOfAny_NoCacheLookup, type, obj);
}
HCIMPLEND
// ChkCast test used for unusual cases (naked type parameters, variant generic types)
// Unlike the ChkCastInterface, ChkCastClass, and ChkCastArray functions,
// this test must deal with all kinds of type tests
-HCIMPL2(Object *, JIT_ChkCastAny, CORINFO_CLASS_HANDLE type, Object *obj)
+HCIMPL2(Object *, JIT_ChkCastAny, CORINFO_CLASS_HANDLE type, Object *pObject)
{
FCALL_CONTRACT;
- if (NULL == obj)
+ if (NULL == pObject)
{
return NULL;
}
- TypeHandle::CastResult result = ObjIsInstanceOfNoGC(obj, TypeHandle(type));
-
+ TypeHandle th = TypeHandle(type);
+ TypeHandle::CastResult result = ObjIsInstanceOfCached(pObject, th);
if (result == TypeHandle::CanCast)
{
- return obj;
+ return pObject;
}
ENDFORBIDGC();
- Object* pRet = HCCALL2(JITutil_ChkCastAny, type, obj);
+ Object* pRet = HCCALL2(JITutil_ChkCastAny_NoCacheLookup, type, pObject);
// Make sure that the fast helper have not lied
_ASSERTE(result != TypeHandle::CannotCast);
return pRet;
{
FCALL_CONTRACT;
- if (obj->GetMethodTable()->IsArray())
- {
- switch (ArrayObjSupportsBizarreInterfaceNoGC(obj, pInterfaceMT)) {
- case TypeHandle::CanCast:
- return obj;
- case TypeHandle::CannotCast:
- return NULL;
- default:
- // fall through to the slow helper
- break;
- }
+ MethodTable* pMT = obj->GetMethodTable();
+ TypeHandle::CastResult result = CastCache::TryGetFromCache(pMT, pInterfaceMT);
+
+ switch (result) {
+ case TypeHandle::CanCast:
+ return obj;
+ case TypeHandle::CannotCast:
+ return NULL;
+ default:
+ // fall through to the slow helper
+ break;
}
ENDFORBIDGC();
- return HCCALL2(JITutil_IsInstanceOfAny, CORINFO_CLASS_HANDLE(pInterfaceMT), obj);
-
+ return HCCALL2(JITutil_IsInstanceOfAny_NoCacheLookup, CORINFO_CLASS_HANDLE(pInterfaceMT), obj);
}
HCIMPLEND
{
FCALL_CONTRACT;
- if (obj->GetMethodTable()->IsArray())
+ MethodTable* pMT = obj->GetMethodTable();
+ TypeHandle::CastResult result = CastCache::TryGetFromCache(pMT, pInterfaceMT);
+
+ if (result == TypeHandle::CanCast)
{
- if (ArrayObjSupportsBizarreInterfaceNoGC(obj, pInterfaceMT) == TypeHandle::CanCast)
- {
- return obj;
- }
+ return obj;
}
ENDFORBIDGC();
- return HCCALL2(JITutil_ChkCastAny, CORINFO_CLASS_HANDLE(pInterfaceMT), obj);
+ return HCCALL2(JITutil_ChkCastAny_NoCacheLookup, CORINFO_CLASS_HANDLE(pInterfaceMT), obj);
}
HCIMPLEND
}
HCIMPLEND
+NOINLINE HCIMPL2(Object*, JITutil_ChkCastAny_NoCacheLookup, CORINFO_CLASS_HANDLE type, Object* obj)
+{
+ FCALL_CONTRACT;
+
+ // This case should be handled by frameless helper
+ _ASSERTE(obj != NULL);
+
+ OBJECTREF oref = ObjectToOBJECTREF(obj);
+ VALIDATEOBJECTREF(oref);
+
+ TypeHandle clsHnd(type);
+
+ HELPER_METHOD_FRAME_BEGIN_RET_1(oref);
+ if (!ObjIsInstanceOfCore(OBJECTREFToObject(oref), clsHnd, TRUE))
+ {
+ UNREACHABLE(); //ObjIsInstanceOf will throw if cast can't be done
+ }
+ HELPER_METHOD_FRAME_END();
+ return OBJECTREFToObject(oref);
+}
+HCIMPLEND
+
+NOINLINE HCIMPL2(Object*, JITutil_IsInstanceOfAny_NoCacheLookup, CORINFO_CLASS_HANDLE type, Object* obj)
+{
+ FCALL_CONTRACT;
+
+ // This case should be handled by frameless helper
+ _ASSERTE(obj != NULL);
+
+ OBJECTREF oref = ObjectToOBJECTREF(obj);
+ VALIDATEOBJECTREF(oref);
+
+ TypeHandle clsHnd(type);
+
+ HELPER_METHOD_FRAME_BEGIN_RET_1(oref);
+ if (!ObjIsInstanceOfCore(OBJECTREFToObject(oref), clsHnd))
+ oref = NULL;
+ HELPER_METHOD_FRAME_END();
+
+ return OBJECTREFToObject(oref);
+}
+HCIMPLEND
//========================================================================
//
GCStress<cfg_any, EeconfigFastGcSPolicy>::MaybeTrigger();
- if (!ObjIsInstanceOf(*pElement, (*pArray)->GetArrayElementTypeHandle()))
+ // call "Core" version directly since all the callers do the "NoGC" call first and that checks the cache
+ if (!ObjIsInstanceOfCore(*pElement, (*pArray)->GetArrayElementTypeHandle()))
COMPlusThrow(kArrayTypeMismatchException);
HELPER_METHOD_FRAME_END();
if (arrayElemTH != TypeHandle(valMT) && arrayElemTH != TypeHandle(g_pObjectClass))
{
- TypeHandle::CastResult result = ObjIsInstanceOfNoGC(val, arrayElemTH);
+ TypeHandle::CastResult result = ObjIsInstanceOfCached(val, arrayElemTH);
if (result != TypeHandle::CanCast)
{
// FCALL_CONTRACT increase ForbidGC count. Normally, HELPER_METHOD_FRAME macros decrease the count.
extern "C" FCDECL2(Object*, JITutil_IsInstanceOfInterface, MethodTable *pInterfaceMT, Object *obj);
extern "C" FCDECL2(Object*, JITutil_ChkCastAny, CORINFO_CLASS_HANDLE type, Object *obj);
extern "C" FCDECL2(Object*, JITutil_IsInstanceOfAny, CORINFO_CLASS_HANDLE type, Object *obj);
+extern "C" FCDECL2(Object*, JITutil_ChkCastAny_NoCacheLookup, CORINFO_CLASS_HANDLE type, Object* obj);
+extern "C" FCDECL2(Object*, JITutil_IsInstanceOfAny_NoCacheLookup, CORINFO_CLASS_HANDLE type, Object* obj);
extern "C" FCDECL1(void, JIT_InternalThrow, unsigned exceptNum);
extern "C" FCDECL1(void*, JIT_InternalThrowFromHelper, unsigned exceptNum);
#endif
BOOL ObjIsInstanceOf(Object *pObject, TypeHandle toTypeHnd, BOOL throwCastException = FALSE);
-EXTERN_C TypeHandle::CastResult STDCALL ObjIsInstanceOfNoGC(Object *pObject, TypeHandle toTypeHnd);
+BOOL ObjIsInstanceOfCore(Object* pObject, TypeHandle toTypeHnd, BOOL throwCastException = FALSE);
+
+EXTERN_C TypeHandle::CastResult STDCALL ObjIsInstanceOfCached(Object *pObject, TypeHandle toTypeHnd);
#ifdef BIT64
class InlinedCallFrame;
#include "stringliteralmap.h"
#include "virtualcallstub.h"
#include "threadsuspend.h"
+#include "castcache.h"
#include "mlinfo.h"
#ifndef DACCESS_COMPILE
#include "comdelegate.h"
// Other values are typically ignored. If using SUSPEND_FOR_APPDOMAIN_SHUTDOWN
// is inappropriate, we can introduce a new flag or hijack an unused one.
ThreadSuspend::SuspendEE(ThreadSuspend::SUSPEND_FOR_APPDOMAIN_SHUTDOWN);
+
+ // drop the cast cache while still in COOP mode.
+ CastCache::FlushCurrentCache();
}
ExecutionManager::Unload(pDomainLoaderAllocatorDestroyIterator);
#include "typestring.h"
#include "typedesc.h"
#include "array.h"
+#include "castcache.h"
#ifdef FEATURE_INTERPRETER
#include "interpreter.h"
if (!pTargetMT->HasVariance())
{
- if (HasTypeEquivalence() || pTargetMT->HasTypeEquivalence())
- {
- if (IsInterface() && IsEquivalentTo(pTargetMT))
- return TRUE;
-
- return ImplementsEquivalentInterface(pTargetMT);
- }
+ if (IsInterface() && IsEquivalentTo(pTargetMT))
+ return TRUE;
- return CanCastToNonVariantInterface(pTargetMT);
+ return ImplementsEquivalentInterface(pTargetMT);
}
else
{
}
CONTRACTL_END
- BOOL returnValue = FALSE;
-
- EEClass *pClass = NULL;
-
- TypeHandlePairList pairList(this, pTargetMT, pVisited);
-
- if (TypeHandlePairList::Exists(pVisited, this, pTargetMT))
- goto Exit;
+ // shortcut when having same types
+ if (this == pTargetMT)
+ {
+ return TRUE;
+ }
- if (GetTypeDefRid() != pTargetMT->GetTypeDefRid() || GetModule() != pTargetMT->GetModule())
+ if (GetTypeDefRid() != pTargetMT->GetTypeDefRid() || GetModule() != pTargetMT->GetModule() ||
+ TypeHandlePairList::Exists(pVisited, this, pTargetMT))
{
- goto Exit;
+ return FALSE;
}
+ EEClass* pClass = NULL;
+ TypeHandlePairList pairList(this, pTargetMT, pVisited);
+ BOOL returnValue = FALSE;
+
{
pClass = pTargetMT->GetClass();
Instantiation inst = GetInstantiation();
}
//==========================================================================================
-TypeHandle::CastResult MethodTable::CanCastToInterfaceNoGC(MethodTable *pTargetMT)
+BOOL MethodTable::CanCastToClassOrInterface(MethodTable* pTargetMT, TypeHandlePairList* pVisited)
{
CONTRACTL
{
- NOTHROW;
- GC_NOTRIGGER;
- MODE_ANY;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
INSTANCE_CHECK;
PRECONDITION(CheckPointer(pTargetMT));
- PRECONDITION(pTargetMT->IsInterface());
+ PRECONDITION(!pTargetMT->IsArray());
PRECONDITION(IsRestored_NoLogging());
}
CONTRACTL_END
- if (!pTargetMT->HasVariance() && !IsArray() && !HasTypeEquivalence() && !pTargetMT->HasTypeEquivalence())
- {
- return CanCastToNonVariantInterface(pTargetMT) ? TypeHandle::CanCast : TypeHandle::CannotCast;
- }
- else
+#ifndef CROSSGEN_COMPILE
+ // we cannot cache T --> Nullable<T> here since result is contextual.
+ // callers should have handled this already according to their rules.
+ _ASSERTE(!Nullable::IsNullableForType(TypeHandle(pTargetMT), this));
+#endif // CROSSGEN_COMPILE
+
+ BOOL result = pTargetMT->IsInterface() ?
+ CanCastToInterface(pTargetMT, pVisited) :
+ CanCastToClass(pTargetMT, pVisited);
+
+ // We only consider type-based conversion rules here.
+ // Therefore a negative result cannot rule out convertibility for ICastable and COM objects
+ if (result || !(pTargetMT->IsInterface() && (this->IsComObjectType() || this->IsICastable())))
{
- // We're conservative on variant interfaces and types with equivalence
- return TypeHandle::MaybeCast;
+ CastCache::TryAddToCache(this, pTargetMT, (BOOL)result);
}
+
+ return result;
}
//==========================================================================================
-TypeHandle::CastResult MethodTable::CanCastToClassNoGC(MethodTable *pTargetMT)
+BOOL MethodTable::ArraySupportsBizarreInterface(MethodTable * pInterfaceMT, TypeHandlePairList* pVisited)
{
- CONTRACTL
- {
- NOTHROW;
- GC_NOTRIGGER;
- MODE_ANY;
- INSTANCE_CHECK;
- PRECONDITION(CheckPointer(pTargetMT));
- PRECONDITION(!pTargetMT->IsArray());
- PRECONDITION(!pTargetMT->IsInterface());
- }
- CONTRACTL_END
+ CONTRACTL {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ PRECONDITION(this->IsArray());
+ PRECONDITION(pInterfaceMT->IsInterface());
+ PRECONDITION(pInterfaceMT->HasInstantiation());
+ } CONTRACTL_END;
- // We're conservative on variant classes
- if (pTargetMT->HasVariance() || g_IBCLogger.InstrEnabled())
+ // IList<T> & IReadOnlyList<T> only supported for SZ_ARRAYS
+ if (this->IsMultiDimArray() ||
+ !IsImplicitInterfaceOfSZArray(pInterfaceMT))
{
- return TypeHandle::MaybeCast;
+ CastCache::TryAddToCache(this, pInterfaceMT, FALSE);
+ return FALSE;
}
- // Type equivalence needs the slow path
- if (HasTypeEquivalence() || pTargetMT->HasTypeEquivalence())
+ BOOL result = TypeDesc::CanCastParam(this->GetApproxArrayElementTypeHandle(), pInterfaceMT->GetInstantiation()[0], pVisited);
+
+ CastCache::TryAddToCache(this, pInterfaceMT, (BOOL)result);
+ return result;
+}
+
+BOOL MethodTable::ArrayIsInstanceOf(TypeHandle toTypeHnd, TypeHandlePairList* pVisited)
+{
+ CONTRACTL{
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ PRECONDITION(this->IsArray());
+ PRECONDITION(toTypeHnd.IsArray());
+ } CONTRACTL_END;
+
+ ArrayTypeDesc* toArrayType = toTypeHnd.AsArray();
+
+ // GetRank touches EEClass. Try to avoid it for SZArrays.
+ if (toArrayType->GetInternalCorElementType() == ELEMENT_TYPE_SZARRAY)
{
- return TypeHandle::MaybeCast;
+ if (this->IsMultiDimArray())
+ {
+ CastCache::TryAddToCache(this, toTypeHnd, FALSE);
+ return TypeHandle::CannotCast;
+ }
}
-
- // If there are no variant type parameters, just chase the hierarchy
else
{
- PTR_VOID pMT = this;
+ if (this->GetRank() != toArrayType->GetRank())
+ {
+ CastCache::TryAddToCache(this, toTypeHnd, FALSE);
+ return TypeHandle::CannotCast;
+ }
+ }
+ _ASSERTE(this->GetRank() == toArrayType->GetRank());
- do {
- if (pMT == pTargetMT)
- return TypeHandle::CanCast;
+ TypeHandle elementTypeHandle = this->GetApproxArrayElementTypeHandle();
+ TypeHandle toElementTypeHandle = toArrayType->GetArrayElementTypeHandle();
- pMT = MethodTable::GetParentMethodTableOrIndirection(pMT);
- } while (pMT);
- }
+ BOOL result = (elementTypeHandle == toElementTypeHandle) ||
+ TypeDesc::CanCastParam(elementTypeHandle, toElementTypeHandle, pVisited);
- return TypeHandle::CannotCast;
+ CastCache::TryAddToCache(this, toTypeHnd, (BOOL)result);
+ return result;
}
+
#include <optdefault.h>
BOOL
//-------------------------------------------------------------------
// CASTING
//
- // There are two variants of each of these methods:
- //
- // CanCastToX
- // - restore encoded pointers on demand
- // - might throw, might trigger GC
- // - return type is boolean (FALSE = cannot cast, TRUE = can cast)
- //
- // CanCastToXNoGC
- // - do not restore encoded pointers on demand
- // - does not throw, does not trigger GC
- // - return type is three-valued (CanCast, CannotCast, MaybeCast)
- // - MaybeCast indicates that the test tripped on an encoded pointer
- // so the caller should now call CanCastToXRestoring if it cares
- //
BOOL CanCastToInterface(MethodTable *pTargetMT, TypeHandlePairList *pVisited = NULL);
BOOL CanCastToClass(MethodTable *pTargetMT, TypeHandlePairList *pVisited = NULL);
- BOOL CanCastToClassOrInterface(MethodTable *pTargetMT, TypeHandlePairList *pVisited);
- BOOL CanCastByVarianceToInterfaceOrDelegate(MethodTable *pTargetMT, TypeHandlePairList *pVisited);
-
- BOOL CanCastToNonVariantInterface(MethodTable *pTargetMT);
+ BOOL CanCastToClassOrInterface(MethodTable *pTargetMT, TypeHandlePairList *pVisited);
+ BOOL MethodTable::ArraySupportsBizarreInterface(MethodTable* pInterfaceMT, TypeHandlePairList* pVisited);
+ BOOL ArrayIsInstanceOf(TypeHandle toTypeHnd, TypeHandlePairList* pVisited);
- TypeHandle::CastResult CanCastToInterfaceNoGC(MethodTable *pTargetMT);
- TypeHandle::CastResult CanCastToClassNoGC(MethodTable *pTargetMT);
- TypeHandle::CastResult CanCastToClassOrInterfaceNoGC(MethodTable *pTargetMT);
+ BOOL CanCastByVarianceToInterfaceOrDelegate(MethodTable* pTargetMT, TypeHandlePairList* pVisited);
+ BOOL CanCastToNonVariantInterface(MethodTable* pTargetMT);
// The inline part of equivalence check.
#ifndef DACCESS_COMPILE
}
}
#endif
-//==========================================================================================
-__forceinline TypeHandle::CastResult MethodTable::CanCastToClassOrInterfaceNoGC(MethodTable *pTargetMT)
-{
- CONTRACTL
- {
- NOTHROW;
- GC_NOTRIGGER;
- MODE_ANY;
- INSTANCE_CHECK;
- PRECONDITION(CheckPointer(pTargetMT));
- PRECONDITION(!pTargetMT->IsArray());
- }
- CONTRACTL_END
-
- if (pTargetMT->IsInterface())
- return CanCastToInterfaceNoGC(pTargetMT);
- else
- return CanCastToClassNoGC(pTargetMT);
-}
-
-//==========================================================================================
-inline BOOL MethodTable::CanCastToClassOrInterface(MethodTable *pTargetMT, TypeHandlePairList *pVisited)
-{
- CONTRACTL
- {
- THROWS;
- GC_TRIGGERS;
- MODE_ANY;
- INSTANCE_CHECK;
- PRECONDITION(CheckPointer(pTargetMT));
- PRECONDITION(!pTargetMT->IsArray());
- PRECONDITION(IsRestored_NoLogging());
- }
- CONTRACTL_END
-
- if (pTargetMT->IsInterface())
- return CanCastToInterface(pTargetMT, pVisited);
- else
- return CanCastToClass(pTargetMT, pVisited);
-}
//==========================================================================================
FORCEINLINE PTR_Module MethodTable::GetGenericsStaticsModuleAndID(DWORD * pID)
if (refType == NULL)
FCThrowRes(kArgumentNullException, W("Arg_InvalidHandle"));
- switch (ObjIsInstanceOfNoGC(objectUNSAFE, refType->GetType())) {
+ switch (ObjIsInstanceOfCached(objectUNSAFE, refType->GetType())) {
case TypeHandle::CanCast:
FC_RETURN_BOOL(true);
case TypeHandle::CannotCast:
#include "peimagelayout.inl"
#include "eventtrace.h"
#include "invokeutil.h"
+#include "castcache.h"
BOOL QCALLTYPE MdUtf8String::EqualsCaseInsensitive(LPCUTF8 szLhs, LPCUTF8 szRhs, INT32 stringNumBytes)
{
TypeHandle fromHandle = refType->GetType();
TypeHandle toHandle = refTarget->GetType();
- BOOL iRetVal = 0;
-
- TypeHandle::CastResult r = fromHandle.CanCastToNoGC(toHandle);
- if (r == TypeHandle::MaybeCast)
- {
- HELPER_METHOD_FRAME_BEGIN_RET_2(refType, refTarget);
- iRetVal = fromHandle.CanCastTo(toHandle);
- HELPER_METHOD_FRAME_END();
- }
- else
+ TypeHandle::CastResult r = fromHandle.CanCastToCached(toHandle);
+ if (r != TypeHandle::MaybeCast)
{
- iRetVal = (r == TypeHandle::CanCast);
+ FC_RETURN_BOOL((BOOL)r);
}
- // We allow T to be cast to Nullable<T>
- if (!iRetVal && Nullable::IsNullableType(toHandle) && !fromHandle.IsTypeDesc())
+ BOOL iRetVal;
+ HELPER_METHOD_FRAME_BEGIN_RET_2(refType, refTarget);
{
- HELPER_METHOD_FRAME_BEGIN_RET_2(refType, refTarget);
- if (Nullable::IsNullableForType(toHandle, fromHandle.AsMethodTable()))
+ // We allow T to be cast to Nullable<T>
+ if (!fromHandle.IsTypeDesc() && Nullable::IsNullableForType(toHandle, fromHandle.AsMethodTable()))
{
+ // do not put this in the cache (see TypeHandle::CanCastTo and ObjIsInstanceOfCore).
iRetVal = TRUE;
}
- HELPER_METHOD_FRAME_END();
+ else
+ {
+ if (fromHandle.IsTypeDesc())
+ {
+ iRetVal = fromHandle.AsTypeDesc()->CanCastTo(toHandle, /* pVisited */ NULL);
+ }
+ else if (toHandle.IsTypeDesc())
+ {
+ iRetVal = FALSE;
+ CastCache::TryAddToCache(fromHandle, toHandle, FALSE);
+ }
+ else
+ {
+ iRetVal = fromHandle.AsMethodTable()->CanCastToClassOrInterface(toHandle.AsMethodTable(), /* pVisited */ NULL);
+ }
+ }
}
-
+ HELPER_METHOD_FRAME_END();
+
FC_RETURN_BOOL(iRetVal);
}
FCIMPLEND
{
FCALL_CONTRACT;
- if (ObjIsInstanceOfNoGC(element, arr->GetArrayElementTypeHandle()) == TypeHandle::CanCast)
+ if (ObjIsInstanceOfCached(element, arr->GetArrayElementTypeHandle()) == TypeHandle::CanCast)
return;
FC_INNER_RETURN_VOID(ArrayTypeCheckSlow(element, arr));
#include "compile.h"
#endif
#include "array.h"
+#include "castcache.h"
#ifndef DACCESS_COMPILE
#ifdef _DEBUG
#ifndef DACCESS_COMPILE
-BOOL TypeDesc::CanCastTo(TypeHandle toType, TypeHandlePairList *pVisited)
+BOOL ArrayTypeDesc::ArrayIsInstanceOf(ArrayTypeDesc *toArrayType, TypeHandlePairList* pVisited)
+{
+ CONTRACTL{
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ PRECONDITION(this->IsArray());
+ PRECONDITION(toArrayType->IsArray());
+ } CONTRACTL_END;
+
+ // GetRank touches EEClass. Try to avoid it for SZArrays.
+ if (toArrayType->GetInternalCorElementType() == ELEMENT_TYPE_SZARRAY)
+ {
+ if (this->GetInternalCorElementType() != ELEMENT_TYPE_SZARRAY)
+ {
+ return TypeHandle::CannotCast;
+ }
+ }
+ else
+ {
+ if (this->GetRank() != toArrayType->GetRank())
+ {
+ return TypeHandle::CannotCast;
+ }
+ }
+ _ASSERTE(this->GetRank() == toArrayType->GetRank());
+
+ TypeHandle elementTypeHandle = this->GetArrayElementTypeHandle();
+ TypeHandle toElementTypeHandle = toArrayType->GetArrayElementTypeHandle();
+
+ BOOL result = (elementTypeHandle == toElementTypeHandle) ||
+ TypeDesc::CanCastParam(elementTypeHandle, toElementTypeHandle, pVisited);
+
+ return result;
+}
+
+BOOL ArrayTypeDesc::ArraySupportsBizarreInterface(MethodTable *pInterfaceMT, TypeHandlePairList *pVisited)
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
- INJECT_FAULT(COMPlusThrowOM());
+
+ PRECONDITION(this->IsArray());
+ PRECONDITION(pInterfaceMT->IsInterface());
+ PRECONDITION(pInterfaceMT->HasInstantiation());
}
CONTRACTL_END
- if (TypeHandle(this) == toType)
- return TRUE;
+ // IList<T> & IReadOnlyList<T> only supported for SZ_ARRAYS
+ if (this->GetInternalCorElementType() != ELEMENT_TYPE_SZARRAY)
+ return FALSE;
- //A boxed variable type can be cast to any of its constraints, or object, if none are specified
- if (IsGenericVariable())
+ if (!IsImplicitInterfaceOfSZArray(pInterfaceMT))
+ return FALSE;
+
+ return TypeDesc::CanCastParam(this->GetTypeParam(), pInterfaceMT->GetInstantiation()[0], pVisited);
+}
+
+BOOL TypeDesc::CanCastTo(TypeHandle toTypeHnd, TypeHandlePairList *pVisited)
+{
+ CONTRACTL
{
- TypeVarTypeDesc *tyvar = (TypeVarTypeDesc*) this;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ INJECT_FAULT(COMPlusThrowOM());
+ }
+ CONTRACTL_END
- DWORD numConstraints;
- TypeHandle *constraints = tyvar->GetConstraints(&numConstraints, CLASS_DEPENDENCIES_LOADED);
+ if (TypeHandle(this) == toTypeHnd)
+ return TRUE;
- if (toType == g_pObjectClass)
- return TRUE;
+ BOOL fCast = FALSE;
- if (toType == g_pValueTypeClass)
+ if (IsArray())
+ {
+ MethodTable* pMT = this->GetMethodTable();
+
+ if (toTypeHnd.IsArray())
{
- mdGenericParam genericParamToken = tyvar->GetToken();
- DWORD flags;
- if (FAILED(tyvar->GetModule()->GetMDImport()->GetGenericParamProps(genericParamToken, NULL, &flags, NULL, NULL, NULL)))
+ // NOTE: in a few cases array type desc may contain a methodtable for object[]
+ // we cannot delegate the cast analysis to the method tables here
+ // we could get a wrong result, so we need to use the typedesc helpers.
+ fCast = dac_cast<PTR_ArrayTypeDesc>(this)->ArrayIsInstanceOf(toTypeHnd.AsArray(), pVisited);
+ }
+ else if (!toTypeHnd.IsTypeDesc())
+ {
+ MethodTable* toMT = toTypeHnd.AsMethodTable();
+ if (toMT->IsInterface() && toMT->HasInstantiation())
{
- return FALSE;
+ // see comment above about ArrayIsInstanceOf
+ fCast = dac_cast<PTR_ArrayTypeDesc>(this)->ArraySupportsBizarreInterface(toMT, pVisited);
+ }
+ else
+ {
+ fCast = pMT->CanCastToClassOrInterface(toMT, pVisited);
}
- DWORD specialConstraints = flags & gpSpecialConstraintMask;
- if ((specialConstraints & gpNotNullableValueTypeConstraint) != 0)
- return TRUE;
}
- if (constraints == NULL)
- return FALSE;
-
- for (DWORD i = 0; i < numConstraints; i++)
- {
- if (constraints[i].CanCastTo(toType, pVisited))
- return TRUE;
- }
- return FALSE;
+ // leafs add cached conversion for the method table.
+ // since we started from a typedesc, add a typedesc conversion too
+ CastCache::TryAddToCache(TypeHandle(this), toTypeHnd, fCast);
+ return fCast;
}
- // If we're not casting to a TypeDesc (i.e. not to a reference array type, variable type etc.)
- // then we must be trying to cast to a class or interface type.
- if (!toType.IsTypeDesc())
+ //A boxed variable type can be cast to any of its constraints, or object, if none are specified
+ if (IsGenericVariable())
{
- if (!IsArray())
- {
- // I am a variable type, pointer type, function pointer type
- // etc. I am not an object or value type. Therefore
- // I can't be cast to an object or value type.
- return FALSE;
- }
-
- MethodTable *pMT = GetMethodTable();
- _ASSERTE(pMT != 0);
+ TypeVarTypeDesc *tyvar = (TypeVarTypeDesc*) this;
- // This does the right thing if 'type' == System.Array or System.Object, System.Clonable ...
- if (pMT->CanCastToClassOrInterface(toType.AsMethodTable(), pVisited) != 0)
+ if (toTypeHnd == g_pObjectClass)
{
- return TRUE;
+ fCast = TRUE;
}
-
- if (IsArray() && toType.AsMethodTable()->IsInterface())
+ else if (toTypeHnd == g_pValueTypeClass)
{
- if (ArraySupportsBizarreInterface((ArrayTypeDesc*)this, toType.AsMethodTable()))
+ mdGenericParam genericParamToken = tyvar->GetToken();
+ DWORD flags;
+ if (!FAILED(tyvar->GetModule()->GetMDImport()->GetGenericParamProps(genericParamToken, NULL, &flags, NULL, NULL, NULL)))
{
- return TRUE;
+ DWORD specialConstraints = flags & gpSpecialConstraintMask;
+ if ((specialConstraints & gpNotNullableValueTypeConstraint) != 0)
+ {
+ fCast = TRUE;
+ }
}
-
}
+ else
+ {
+ DWORD numConstraints;
+ TypeHandle* constraints = tyvar->GetConstraints(&numConstraints, CLASS_DEPENDENCIES_LOADED);
- return FALSE;
+ if (constraints != NULL)
+ {
+ for (DWORD i = 0; i < numConstraints; i++)
+ {
+ if (constraints[i].CanCastTo(toTypeHnd, pVisited))
+ {
+ fCast = TRUE;
+ break;
+ }
+ }
+ }
+ }
}
-
- TypeDesc* toTypeDesc = toType.AsTypeDesc();
-
- CorElementType toKind = toTypeDesc->GetInternalCorElementType();
- CorElementType fromKind = GetInternalCorElementType();
-
- // The element kinds must match, only exception is that SZARRAY matches a one dimension ARRAY
- if (!(toKind == fromKind || (toKind == ELEMENT_TYPE_ARRAY && fromKind == ELEMENT_TYPE_SZARRAY)))
- return FALSE;
-
- switch (toKind)
+ else if (toTypeHnd.IsTypeDesc())
{
- case ELEMENT_TYPE_ARRAY:
- if (dac_cast<PTR_ArrayTypeDesc>(this)->GetRank() != dac_cast<PTR_ArrayTypeDesc>(toTypeDesc)->GetRank())
- return FALSE;
- // fall through
- case ELEMENT_TYPE_SZARRAY:
- case ELEMENT_TYPE_BYREF:
- case ELEMENT_TYPE_PTR:
- return TypeDesc::CanCastParam(dac_cast<PTR_ParamTypeDesc>(this)->GetTypeParam(), dac_cast<PTR_ParamTypeDesc>(toTypeDesc)->GetTypeParam(), pVisited);
+ TypeDesc* toTypeDesc = toTypeHnd.AsTypeDesc();
+ CorElementType toKind = toTypeDesc->GetInternalCorElementType();
+ CorElementType fromKind = GetInternalCorElementType();
- case ELEMENT_TYPE_VAR:
- case ELEMENT_TYPE_MVAR:
- case ELEMENT_TYPE_FNPTR:
- return FALSE;
+ // The element kinds must match
+ if (toKind == fromKind)
+ {
+ switch (toKind)
+ {
+ case ELEMENT_TYPE_BYREF:
+ case ELEMENT_TYPE_PTR:
+ fCast = TypeDesc::CanCastParam(dac_cast<PTR_ParamTypeDesc>(this)->GetTypeParam(), dac_cast<PTR_ParamTypeDesc>(toTypeDesc)->GetTypeParam(), pVisited);
+ break;
+ case ELEMENT_TYPE_VAR:
+ case ELEMENT_TYPE_MVAR:
+ case ELEMENT_TYPE_FNPTR:
+ fCast = FALSE;
+ break;
+ default:
+ BAD_FORMAT_NOTHROW_ASSERT(toKind == ELEMENT_TYPE_TYPEDBYREF || CorTypeInfo::IsPrimitiveType(toKind));
+ // array cast should have been handled above
+ _ASSERTE(toKind != ELEMENT_TYPE_ARRAY);
+ _ASSERTE(toKind != ELEMENT_TYPE_SZARRAY);
- default:
- BAD_FORMAT_NOTHROW_ASSERT(toKind == ELEMENT_TYPE_TYPEDBYREF || CorTypeInfo::IsPrimitiveType(toKind));
- return TRUE;
+ fCast = TRUE;
+ }
+ }
}
+
+ CastCache::TryAddToCache(TypeHandle(this), toTypeHnd, fCast);
+ return fCast;
}
BOOL TypeDesc::CanCastParam(TypeHandle fromParam, TypeHandle toParam, TypeHandlePairList *pVisited)
return FALSE;
}
-TypeHandle::CastResult TypeDesc::CanCastToNoGC(TypeHandle toType)
-{
- CONTRACTL
- {
- NOTHROW;
- GC_NOTRIGGER;
- FORBID_FAULT;
- }
- CONTRACTL_END
-
- if (TypeHandle(this) == toType)
- return TypeHandle::CanCast;
-
- //A boxed variable type can be cast to any of its constraints, or object, if none are specified
- if (IsGenericVariable())
- {
- TypeVarTypeDesc *tyvar = (TypeVarTypeDesc*) this;
-
- if (!tyvar->ConstraintsLoaded())
- return TypeHandle::MaybeCast;
-
- DWORD numConstraints;
- TypeHandle *constraints = tyvar->GetCachedConstraints(&numConstraints);
-
- if (toType == g_pObjectClass)
- return TypeHandle::CanCast;
-
- if (toType == g_pValueTypeClass)
- return TypeHandle::MaybeCast;
-
- if (constraints == NULL)
- return TypeHandle::CannotCast;
-
- for (DWORD i = 0; i < numConstraints; i++)
- {
- if (constraints[i].CanCastToNoGC(toType) == TypeHandle::CanCast)
- return TypeHandle::CanCast;
- }
- return TypeHandle::MaybeCast;
- }
-
- // If we're not casting to a TypeDesc (i.e. not to a reference array type, variable type etc.)
- // then we must be trying to cast to a class or interface type.
- if (!toType.IsTypeDesc())
- {
- if (!IsArray())
- {
- // I am a variable type, pointer type, function pointer type
- // etc. I am not an object or value type. Therefore
- // I can't be cast to an object or value type.
- return TypeHandle::CannotCast;
- }
-
- MethodTable *pMT = GetMethodTable();
- _ASSERTE(pMT != 0);
-
- // This does the right thing if 'type' == System.Array or System.Object, System.Clonable ...
- return pMT->CanCastToClassOrInterfaceNoGC(toType.AsMethodTable());
- }
-
- TypeDesc* toTypeDesc = toType.AsTypeDesc();
-
- CorElementType toKind = toTypeDesc->GetInternalCorElementType();
- CorElementType fromKind = GetInternalCorElementType();
-
- // The element kinds must match, only exception is that SZARRAY matches a one dimension ARRAY
- if (!(toKind == fromKind || (toKind == ELEMENT_TYPE_ARRAY && fromKind == ELEMENT_TYPE_SZARRAY)))
- return TypeHandle::CannotCast;
-
- switch (toKind)
- {
- case ELEMENT_TYPE_ARRAY:
- if (dac_cast<PTR_ArrayTypeDesc>(this)->GetRank() != dac_cast<PTR_ArrayTypeDesc>(toTypeDesc)->GetRank())
- return TypeHandle::CannotCast;
- // fall through
- case ELEMENT_TYPE_SZARRAY:
- case ELEMENT_TYPE_BYREF:
- case ELEMENT_TYPE_PTR:
- return TypeDesc::CanCastParamNoGC(dac_cast<PTR_ParamTypeDesc>(this)->GetTypeParam(), dac_cast<PTR_ParamTypeDesc>(toTypeDesc)->GetTypeParam());
-
- case ELEMENT_TYPE_VAR:
- case ELEMENT_TYPE_MVAR:
- case ELEMENT_TYPE_FNPTR:
- return TypeHandle::CannotCast;
-
- default:
- BAD_FORMAT_NOTHROW_ASSERT(toKind == ELEMENT_TYPE_TYPEDBYREF || CorTypeInfo::IsPrimitiveType_NoThrow(toKind));
- return TypeHandle::CanCast;
- }
-}
-
-TypeHandle::CastResult TypeDesc::CanCastParamNoGC(TypeHandle fromParam, TypeHandle toParam)
+TypeHandle::CastResult TypeDesc::CanCastToCached(TypeHandle toType)
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
+ MODE_COOPERATIVE;
FORBID_FAULT;
}
CONTRACTL_END
- // While boxed value classes inherit from object their
- // unboxed versions do not. Parameterized types have the
- // unboxed version, thus, if the from type parameter is value
- // class then only an exact match works.
- if (fromParam == toParam)
- return TypeHandle::CanCast;
-
- // Object parameters dont need an exact match but only inheritance, check for that
- CorElementType fromParamCorType = fromParam.GetVerifierCorElementType();
- if (CorTypeInfo::IsObjRef_NoThrow(fromParamCorType))
- {
- return fromParam.CanCastToNoGC(toParam);
- }
- else if (CorTypeInfo::IsGenericVariable_NoThrow(fromParamCorType))
- {
- TypeVarTypeDesc* varFromParam = fromParam.AsGenericVariable();
-
- if (!varFromParam->ConstraintsLoaded())
- return TypeHandle::MaybeCast;
-
- if (!varFromParam->ConstrainedAsObjRef())
- return TypeHandle::CannotCast;
-
- return fromParam.CanCastToNoGC(toParam);
- }
- else if (CorTypeInfo::IsPrimitiveType_NoThrow(fromParamCorType))
- {
- CorElementType toParamCorType = toParam.GetVerifierCorElementType();
- if(CorTypeInfo::IsPrimitiveType_NoThrow(toParamCorType))
- {
- if (toParamCorType == fromParamCorType)
- return TypeHandle::CanCast;
-
- // Primitive types such as E_T_I4 and E_T_U4 are interchangeable
- // Enums with interchangeable underlying types are interchangable
- // BOOL is NOT interchangeable with I1/U1, neither CHAR -- with I2/U2
- if((toParamCorType != ELEMENT_TYPE_BOOLEAN)
- &&(fromParamCorType != ELEMENT_TYPE_BOOLEAN)
- &&(toParamCorType != ELEMENT_TYPE_CHAR)
- &&(fromParamCorType != ELEMENT_TYPE_CHAR))
- {
- if ((CorTypeInfo::Size_NoThrow(toParamCorType) == CorTypeInfo::Size_NoThrow(fromParamCorType))
- && (CorTypeInfo::IsFloat_NoThrow(toParamCorType) == CorTypeInfo::IsFloat_NoThrow(fromParamCorType)))
- {
- return TypeHandle::CanCast;
- }
- }
- } // end if(CorTypeInfo::IsPrimitiveType(toParamCorType))
- } // end if(CorTypeInfo::IsPrimitiveType(fromParamCorType))
- else
- {
- // Types with equivalence need the slow path
- MethodTable * pFromMT = fromParam.GetMethodTable();
- if (pFromMT != NULL && pFromMT->HasTypeEquivalence())
- return TypeHandle::MaybeCast;
- MethodTable * pToMT = toParam.GetMethodTable();
- if (pToMT != NULL && pToMT->HasTypeEquivalence())
- return TypeHandle::MaybeCast;
- }
-
- // Anything else is not a match.
- return TypeHandle::CannotCast;
+ return CastCache::TryGetFromCache(TypeHandle(this), toType);
}
BOOL TypeDesc::IsEquivalentTo(TypeHandle type COMMA_INDEBUG(TypeHandlePairList *pVisited))
// There are two variants of the "CanCastTo" method:
//
// CanCastTo
- // - restore encoded pointers on demand
// - might throw, might trigger GC
// - return type is boolean (FALSE = cannot cast, TRUE = can cast)
//
- // CanCastToNoGC
- // - do not restore encoded pointers on demand
+ // CanCastToCached
// - does not throw, does not trigger GC
// - return type is three-valued (CanCast, CannotCast, MaybeCast)
- // - MaybeCast indicates that the test tripped on an encoded pointer
+ //
+ // MaybeCast indicates an inconclusive result
+ // - the test result could not be obtained from a cache
// so the caller should now call CanCastTo if it cares
//
BOOL CanCastTo(TypeHandle type, TypeHandlePairList *pVisited);
- TypeHandle::CastResult CanCastToNoGC(TypeHandle type);
+ TypeHandle::CastResult CanCastToCached(TypeHandle type);
static BOOL CanCastParam(TypeHandle fromParam, TypeHandle toParam, TypeHandlePairList *pVisited);
- static TypeHandle::CastResult CanCastParamNoGC(TypeHandle fromParam, TypeHandle toParam);
-
+
#ifndef DACCESS_COMPILE
BOOL IsEquivalentTo(TypeHandle type COMMA_INDEBUG(TypeHandlePairList *pVisited));
#endif
return g_pArrayClass;
}
+ BOOL ArrayIsInstanceOf(ArrayTypeDesc* toArrayType, TypeHandlePairList* pVisited);
+ BOOL ArraySupportsBizarreInterface(MethodTable* pInterfaceMT, TypeHandlePairList* pVisited);
+
#ifdef FEATURE_COMINTEROP
ComCallWrapperTemplate *GetComCallWrapperTemplate()
{
#include "typestring.h"
#include "classloadlevel.h"
#include "array.h"
+#include "castcache.h"
+
#ifdef FEATURE_PREJIT
#include "zapsig.h"
#endif
{
THROWS;
GC_TRIGGERS;
+ MODE_ANY;
INJECT_FAULT(COMPlusThrowOM());
LOADS_TYPE(CLASS_DEPENDENCIES_LOADED);
CONTRACTL_END
if (*this == type)
- return(true);
+ return true;
- if (IsTypeDesc())
- return AsTypeDesc()->CanCastTo(type, pVisited);
-
- if (type.IsTypeDesc())
- return(false);
+ if (!IsTypeDesc() && type.IsTypeDesc())
+ return false;
+
+ {
+ GCX_COOP();
+
+ TypeHandle::CastResult result = CastCache::TryGetFromCache(*this, type);
+ if (result != TypeHandle::MaybeCast)
+ {
+ return (BOOL)result;
+ }
- return AsMethodTable()->CanCastToClassOrInterface(type.AsMethodTable(), pVisited);
+ if (IsTypeDesc())
+ return AsTypeDesc()->CanCastTo(type, pVisited);
+
+#ifndef CROSSGEN_COMPILE
+ // we check nullable case first because it is not cacheable.
+ // object castability and type castability disagree on T --> Nullable<T>,
+ // so we can't put this in the cache
+ if (Nullable::IsNullableForType(type, AsMethodTable()))
+ {
+ // do not allow type T to be cast to Nullable<T>
+ return FALSE;
+ }
+#endif //!CROSSGEN_COMPILE
+
+ return AsMethodTable()->CanCastToClassOrInterface(type.AsMethodTable(), pVisited);
+ }
}
#include <optsmallperfcritical.h>
-TypeHandle::CastResult TypeHandle::CanCastToNoGC(TypeHandle type) const
+TypeHandle::CastResult TypeHandle::CanCastToCached(TypeHandle type) const
{
- LIMITED_METHOD_CONTRACT;
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_COOPERATIVE;
+ }
+ CONTRACTL_END;
if (*this == type)
- return(CanCast);
+ return CanCast;
- if (IsTypeDesc())
- return AsTypeDesc()->CanCastToNoGC(type);
-
- if (type.IsTypeDesc())
- return(CannotCast);
+ if (!IsTypeDesc() && type.IsTypeDesc())
+ return CannotCast;
- return AsMethodTable()->CanCastToClassOrInterfaceNoGC(type.AsMethodTable());
+ return CastCache::TryGetFromCache(*this, type);
}
#include <optdefault.h>
{
if (tb.IsArray())
return MergeArrayTypeHandlesToCommonParent(ta, tb);
- else if (tb.IsInterface())
+
+ if (tb.IsInterface() && tb.HasInstantiation())
{
//Check to see if we can merge the array to a common interface (such as Derived[] and IList<Base>)
- if (ArraySupportsBizarreInterface(ta.AsArray(), tb.AsMethodTable()))
+ if (ta.AsArray()->ArraySupportsBizarreInterface(tb.AsMethodTable(), /* pVisited */ NULL))
return tb;
}
+
ta = TypeHandle(g_pArrayClass); // keep merging from here.
}
else if (tb.IsArray())
{
- if (ta.IsInterface() && ArraySupportsBizarreInterface(tb.AsArray(), ta.AsMethodTable()))
- return ta;
+ if (ta.IsInterface() && ta.HasInstantiation())
+ {
+ //Check to see if we can merge the array to a common interface (such as Derived[] and IList<Base>)
+ if (tb.AsArray()->ArraySupportsBizarreInterface(ta.AsMethodTable(), /* pVisited */ NULL))
+ return ta;
+ }
tb = TypeHandle(g_pArrayClass);
}
// There are two variants of the "CanCastTo" method:
//
// CanCastTo
- // - restore encoded pointers on demand
// - might throw, might trigger GC
// - return type is boolean (FALSE = cannot cast, TRUE = can cast)
//
- // CanCastToNoGC
- // - do not restore encoded pointers on demand
+ // CanCastToCached
// - does not throw, does not trigger GC
// - return type is three-valued (CanCast, CannotCast, MaybeCast)
- // - MaybeCast indicates that the test tripped on an encoded pointer
+ //
+ // MaybeCast indicates an inconclusive result
+ // - the test result could not be obtained from a cache
// so the caller should now call CanCastTo if it cares
//
// Note that if the TypeHandle is a valuetype, the caller is responsible
BOOL CanCastTo(TypeHandle type, TypeHandlePairList *pVisited = NULL) const;
BOOL IsBoxedAndCanCastTo(TypeHandle type, TypeHandlePairList *pVisited) const;
- CastResult CanCastToNoGC(TypeHandle type) const;
+ CastResult CanCastToCached(TypeHandle type) const;
#ifndef DACCESS_COMPILE
// Type equivalence based on Guid and TypeIdentifier attributes