Faster conversions (dotnet/coreclr#23548)
authorVladimir Sadov <vsadov@microsoft.com>
Sat, 26 Oct 2019 01:45:06 +0000 (18:45 -0700)
committerGitHub <noreply@github.com>
Sat, 26 Oct 2019 01:45:06 +0000 (18:45 -0700)
Improved performance of complex casts by caching results of cast analysis.

* Easy out for same types.
* Introduce cast cache
* Use managed heap for the cache
* Fixes. Get rid of a fake array typedesc.
* TypeDesc conversions caching
* Removed NoGC helpers
* Cache typedesc conversions
* Renamed remaining trivial NoGC casting helpers
* Some cleanups. Comments, redundant code.
* PR feedback.

Commit migrated from https://github.com/dotnet/coreclr/commit/a55a7eb68951ca47b0241ef6cb79287f9fd554e1

31 files changed:
src/coreclr/src/classlibnative/bcltype/arraynative.cpp
src/coreclr/src/debug/ee/funceval.cpp
src/coreclr/src/vm/CMakeLists.txt
src/coreclr/src/vm/amd64/JitHelpers_Fast.asm
src/coreclr/src/vm/amd64/jithelpers_fast.S
src/coreclr/src/vm/arm/asmhelpers.S
src/coreclr/src/vm/arm/asmhelpers.asm
src/coreclr/src/vm/arm64/asmhelpers.S
src/coreclr/src/vm/arm64/asmhelpers.asm
src/coreclr/src/vm/array.cpp
src/coreclr/src/vm/array.h
src/coreclr/src/vm/castcache.cpp [new file with mode: 0644]
src/coreclr/src/vm/castcache.h [new file with mode: 0644]
src/coreclr/src/vm/ceemain.cpp
src/coreclr/src/vm/crossgen/CMakeLists.txt
src/coreclr/src/vm/i386/jithelp.asm
src/coreclr/src/vm/i386/jitinterfacex86.cpp
src/coreclr/src/vm/i386/stublinkerx86.cpp
src/coreclr/src/vm/jithelpers.cpp
src/coreclr/src/vm/jitinterface.h
src/coreclr/src/vm/loaderallocator.cpp
src/coreclr/src/vm/methodtable.cpp
src/coreclr/src/vm/methodtable.h
src/coreclr/src/vm/methodtable.inl
src/coreclr/src/vm/reflectioninvocation.cpp
src/coreclr/src/vm/runtimehandles.cpp
src/coreclr/src/vm/stubhelpers.cpp
src/coreclr/src/vm/typedesc.cpp
src/coreclr/src/vm/typedesc.h
src/coreclr/src/vm/typehandle.cpp
src/coreclr/src/vm/typehandle.h

index 3f64dbc..a02048a 100644 (file)
@@ -247,7 +247,7 @@ ArrayNative::AssignArrayEnum ArrayNative::CanAssignArrayTypeNoGC(const BASEARRAY
     // 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;
@@ -258,9 +258,9 @@ ArrayNative::AssignArrayEnum ArrayNative::CanAssignArrayTypeNoGC(const BASEARRAY
     // 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;
@@ -284,11 +284,11 @@ ArrayNative::AssignArrayEnum ArrayNative::CanAssignArrayTypeNoGC(const BASEARRAY
     }
     
     // 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.
@@ -961,9 +961,6 @@ FCIMPL6(void, ArrayNative::ArrayCopy, ArrayBase* m_pSrc, INT32 m_iSrcIndex, Arra
         FC_GC_POLL();
         return;
     }
-    else if (reliable) {
-        FCThrowResVoid(kArrayTypeMismatchException, W("ArrayTypeMismatch_ConstrainedCopy"));
-    }
 
     HELPER_METHOD_FRAME_BEGIN_PROTECT(gc);
     if (r == AssignDontKnow)
@@ -972,12 +969,13 @@ FCIMPL6(void, ArrayNative::ArrayCopy, ArrayBase* m_pSrc, INT32 m_iSrcIndex, Arra
     }
     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)
     {
@@ -1271,7 +1269,7 @@ FCIMPL2(void, ArrayNative::SetValue, TypedByRef * target, Object* objUNSAFE)
     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);
index efce526..e6bb29b 100644 (file)
@@ -2781,7 +2781,7 @@ void PackArgumentArray(DebuggerEval *pDE,
                     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"));
                     }
index cebf370..ed282a5 100644 (file)
@@ -40,6 +40,7 @@ set(VM_SOURCES_DAC_AND_WKS_COMMON
     assembly.cpp
     baseassemblyspec.cpp
     binder.cpp
+    castcache.cpp
     callcounter.cpp
     ceeload.cpp
     class.cpp
@@ -141,6 +142,7 @@ set(VM_HEADERS_DAC_AND_WKS_COMMON
     baseassemblyspec.h
     baseassemblyspec.inl
     binder.h
+    castcache.h
     ceeload.h
     ceeload.inl
     class.h
index 6d84939..d72d56c 100644 (file)
@@ -229,7 +229,7 @@ endm
 
 ; 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
@@ -766,7 +766,7 @@ g_pObjectClass      equ     ?g_pObjectClass@@3PEAVMethodTable@@EA
 
 EXTERN  g_pObjectClass:qword
 extern ArrayStoreCheck:proc
-extern ObjIsInstanceOfNoGC:proc
+extern ObjIsInstanceOfCached:proc
 
 ; TODO: put definition for this in asmconstants.h
 CanCast equ     1
@@ -813,7 +813,7 @@ LEAF_ENTRY JIT_Stelem_Ref, _TEXT
         cmp     r9, [g_pObjectClass]
         je      DoWrite
 
-        jmp     JIT_Stelem_Ref__ObjIsInstanceOfNoGC_Helper
+        jmp     JIT_Stelem_Ref__ObjIsInstanceOfCached_Helper
                                 
     ThrowNullReferenceException:
         mov     rcx, CORINFO_NullReferenceException_ASM
@@ -824,7 +824,7 @@ LEAF_ENTRY JIT_Stelem_Ref, _TEXT
         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
@@ -835,8 +835,8 @@ NESTED_ENTRY JIT_Stelem_Ref__ObjIsInstanceOfNoGC_Helper, _TEXT
         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]
@@ -855,7 +855,7 @@ NESTED_ENTRY JIT_Stelem_Ref__ObjIsInstanceOfNoGC_Helper, _TEXT
     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
index 26dcad9..1103c60 100644 (file)
@@ -453,7 +453,7 @@ LEAF_ENTRY JIT_Stelem_Ref, _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 
@@ -464,7 +464,7 @@ LEAF_ENTRY JIT_Stelem_Ref, _TEXT
         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
@@ -478,8 +478,8 @@ LEAF_ENTRY JIT_Stelem_Ref__ObjIsInstanceOfNoGC_Helper, _TEXT
         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]
@@ -503,7 +503,7 @@ LEAF_ENTRY JIT_Stelem_Ref__ObjIsInstanceOfNoGC_Helper, _TEXT
 
     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
index 1234813..86b8006 100644 (file)
@@ -1120,10 +1120,10 @@ LOCAL_LABEL(ThrowIndexOutOfRangeException):
     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
 
index 21e0f65..72d685f 100644 (file)
@@ -24,7 +24,7 @@
     IMPORT PreStubWorker
     IMPORT PreStubGetMethodDescForCompactEntryPoint
     IMPORT NDirectImportWorker
-    IMPORT ObjIsInstanceOfNoGC
+    IMPORT ObjIsInstanceOfCached
     IMPORT ArrayStoreCheck
     IMPORT VSD_ResolveWorker
     IMPORT $g_pObjectClass
@@ -1524,10 +1524,10 @@ ThrowIndexOutOfRangeException
     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
 
index f323b94..65f7410 100644 (file)
@@ -1344,10 +1344,10 @@ NESTED_ENTRY JIT_Stelem_Ref_NotExactMatch, _TEXT, NoHandler
     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
 
index 288ce57..0e60752 100644 (file)
@@ -36,7 +36,7 @@
     IMPORT DynamicHelperWorker
 #endif
 
-    IMPORT ObjIsInstanceOfNoGC
+    IMPORT ObjIsInstanceOfCached
     IMPORT ArrayStoreCheck
     SETALIAS g_pObjectClass,  ?g_pObjectClass@@3PEAVMethodTable@@EA 
     IMPORT  $g_pObjectClass
@@ -1582,10 +1582,10 @@ ThrowIndexOutOfRangeException
     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
 
index 194d346..7e2674c 100644 (file)
@@ -1289,6 +1289,7 @@ BOOL IsImplicitInterfaceOfSZArray(MethodTable *pInterfaceMT)
 {
     LIMITED_METHOD_CONTRACT;
     PRECONDITION(pInterfaceMT->IsInterface());
+    PRECONDITION(pInterfaceMT->HasInstantiation());
 
     // Is target interface Anything<T> in mscorlib?
     if (!pInterfaceMT->HasInstantiation() || !pInterfaceMT->GetModule()->IsSystem())
@@ -1304,42 +1305,6 @@ BOOL IsImplicitInterfaceOfSZArray(MethodTable *pInterfaceMT)
             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:
index 35b927e..080f07d 100644 (file)
@@ -105,7 +105,6 @@ Stub *GenerateArrayOpStub(ArrayMethodDesc* pMD);
 
 
 BOOL IsImplicitInterfaceOfSZArray(MethodTable *pIntfMT);
-BOOL ArraySupportsBizarreInterface(ArrayTypeDesc *pArrayTypeDesc, MethodTable *pInterfaceMT);
 MethodDesc* GetActualImplementationForArrayGenericIListOrIReadOnlyListMethod(MethodDesc *pItfcMeth, TypeHandle theT);
 
 CorElementType GetNormalizedIntegralArrayElementType(CorElementType elementType);
diff --git a/src/coreclr/src/vm/castcache.cpp b/src/coreclr/src/vm/castcache.cpp
new file mode 100644 (file)
index 0000000..bdf5750
--- /dev/null
@@ -0,0 +1,302 @@
+// 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
diff --git a/src/coreclr/src/vm/castcache.h b/src/coreclr/src/vm/castcache.h
new file mode 100644 (file)
index 0000000..a1159b3
--- /dev/null
@@ -0,0 +1,341 @@
+// 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
index ba29556..cdb75b1 100644 (file)
 #include "threadsuspend.h"
 #include "disassembler.h"
 #include "jithost.h"
+#include "castcache.h"
 
 #ifndef FEATURE_PAL
 #include "dwreport.h"
@@ -961,6 +962,9 @@ void EEStartupHelper(COINITIEE fFlags)
         // 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)
index 949e96e..f236398 100644 (file)
@@ -5,6 +5,7 @@ set(VM_CROSSGEN_SOURCES
   ../assemblyspec.cpp
   ../baseassemblyspec.cpp
   ../binder.cpp
+  ../castcache.cpp
   ../ceeload.cpp
   ../ceemain.cpp
   ../class.cpp
index 463d9b6..318ef46 100644 (file)
@@ -1294,7 +1294,7 @@ ret
 _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.
index 36296f3..81337e3 100644 (file)
@@ -153,7 +153,7 @@ NotExactMatch:
         push EDX                    // element type handle
         push EAX                    // object
 
-        call ObjIsInstanceOfNoGC
+        call ObjIsInstanceOfCached
 
         pop ECX                     // caller-restore ECX and EDX
         pop EDX
index 3ad07da..a144b65 100644 (file)
@@ -5020,7 +5020,7 @@ VOID StubLinkerCPU::EmitArrayOpStub(const ArrayOpScript* pArrayOpScript)
             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  
                                    
@@ -5060,8 +5060,8 @@ VOID StubLinkerCPU::EmitArrayOpStub(const ArrayOpScript* pArrayOpScript)
             // 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);
                                                
index 521afb7..92e26ce 100644 (file)
@@ -54,6 +54,7 @@
 #endif // HAVE_GCCOVER
 
 #include "runtimehandles.h"
+#include "castcache.h"
 
 //========================================================================
 //
@@ -2070,141 +2071,20 @@ HCIMPLEND_RAW
 //
 //========================================================================
 
-// 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;
@@ -2214,66 +2094,89 @@ BOOL ObjIsInstanceOf(Object *pObject, TypeHandle toTypeHnd, BOOL throwCastExcept
     } 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);
     }    
@@ -2283,6 +2186,27 @@ BOOL ObjIsInstanceOf(Object *pObject, TypeHandle toTypeHnd, BOOL throwCastExcept
     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
 //
@@ -2436,19 +2360,15 @@ HCIMPL2(Object *, JIT_ChkCastArray, CORINFO_CLASS_HANDLE type, Object *pObject)
         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;
@@ -2479,7 +2399,10 @@ HCIMPL2(Object *, JIT_IsInstanceOfArray, CORINFO_CLASS_HANDLE type, Object *pObj
     }
     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:
@@ -2491,7 +2414,7 @@ HCIMPL2(Object *, JIT_IsInstanceOfArray, CORINFO_CLASS_HANDLE type, Object *pObj
     }
 
     ENDFORBIDGC();
-    return HCCALL2(JITutil_IsInstanceOfAny, type, pObject);
+    return HCCALL2(JITutil_IsInstanceOfAny_NoCacheLookup, type, pObject);
 }
 HCIMPLEND
 
@@ -2508,7 +2431,8 @@ HCIMPL2(Object *, JIT_IsInstanceOfAny, CORINFO_CLASS_HANDLE type, Object* obj)
         return NULL;
     }
 
-    switch (ObjIsInstanceOfNoGC(obj, TypeHandle(type))) {
+    TypeHandle th = TypeHandle(type);
+    switch (ObjIsInstanceOfCached(obj, th)) {
     case TypeHandle::CanCast:
         return obj;
     case TypeHandle::CannotCast:
@@ -2519,31 +2443,31 @@ HCIMPL2(Object *, JIT_IsInstanceOfAny, CORINFO_CLASS_HANDLE type, Object* obj)
     }
 
     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;
@@ -2555,22 +2479,21 @@ NOINLINE HCIMPL2(Object *, JITutil_IsInstanceOfInterface, MethodTable *pInterfac
 {
     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
 
@@ -2578,16 +2501,16 @@ NOINLINE HCIMPL2(Object *, JITutil_ChkCastInterface, MethodTable *pInterfaceMT,
 {
     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
 
@@ -2642,7 +2565,49 @@ NOINLINE HCIMPL2(Object *, JITutil_IsInstanceOfAny, CORINFO_CLASS_HANDLE type, O
 }
 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
 
 //========================================================================
 //
@@ -3274,7 +3239,8 @@ HCIMPL2(LPVOID, ArrayStoreCheck, Object** pElement, PtrArray** pArray)
 
     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();
@@ -3306,7 +3272,7 @@ HCIMPL3(void, JIT_Stelem_Ref_Portable, PtrArray* array, unsigned idx, Object *va
 
         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.
index 41c3f36..c56f82d 100644 (file)
@@ -283,6 +283,8 @@ extern "C" FCDECL2(Object*, JITutil_ChkCastInterface, MethodTable *pInterfaceMT,
 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);
@@ -1670,7 +1672,9 @@ EXTERN_C FCDECL0(VOID, JIT_PollGC_Nop);
 #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;
index 12e14bd..58e08b3 100644 (file)
@@ -7,6 +7,7 @@
 #include "stringliteralmap.h"
 #include "virtualcallstub.h"
 #include "threadsuspend.h"
+#include "castcache.h"
 #include "mlinfo.h"
 #ifndef DACCESS_COMPILE
 #include "comdelegate.h"
@@ -613,6 +614,9 @@ void LoaderAllocator::GCLoaderAllocators(LoaderAllocator* pOriginalLoaderAllocat
                         // 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);
index ddf37e8..9eb1c9e 100644 (file)
@@ -61,6 +61,7 @@
 #include "typestring.h"
 #include "typedesc.h"
 #include "array.h"
+#include "castcache.h"
 
 #ifdef FEATURE_INTERPRETER
 #include "interpreter.h"
@@ -1479,15 +1480,10 @@ BOOL MethodTable::CanCastToInterface(MethodTable *pTargetMT, TypeHandlePairList
 
     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
     {
@@ -1520,20 +1516,22 @@ BOOL MethodTable::CanCastByVarianceToInterfaceOrDelegate(MethodTable *pTargetMT,
     }
     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();
@@ -1658,73 +1656,107 @@ BOOL MethodTable::CanCastToNonVariantInterface(MethodTable *pTargetMT)
 }
 
 //==========================================================================================
-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 
index 6522a12..34c37c4 100644 (file)
@@ -1972,30 +1972,14 @@ public:
     //-------------------------------------------------------------------
     // 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
index a90207e..98f034b 100644 (file)
@@ -1611,46 +1611,6 @@ inline void MethodTable::UnBoxIntoUnchecked(void *dest, OBJECTREF src)
     }
 }
 #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)
index 4aed9e0..3ff07e8 100644 (file)
@@ -606,7 +606,7 @@ FCIMPL2(FC_BOOL_RET, RuntimeTypeHandle::IsInstanceOfType, ReflectClassBaseObject
     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:
index c22be0f..5f4d0f9 100644 (file)
@@ -29,6 +29,7 @@
 #include "peimagelayout.inl"
 #include "eventtrace.h"
 #include "invokeutil.h"
+#include "castcache.h"
 
 BOOL QCALLTYPE MdUtf8String::EqualsCaseInsensitive(LPCUTF8 szLhs, LPCUTF8 szRhs, INT32 stringNumBytes)
 {
@@ -1360,31 +1361,40 @@ FCIMPL2(FC_BOOL_RET, RuntimeTypeHandle::CanCastTo, ReflectClassBaseObject *pType
     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
index 9ecad9d..ee31010 100644 (file)
@@ -1729,7 +1729,7 @@ FCIMPL2(void, StubHelpers::ArrayTypeCheck, Object* element, PtrArray* arr)
 {
     FCALL_CONTRACT;
 
-    if (ObjIsInstanceOfNoGC(element, arr->GetArrayElementTypeHandle()) == TypeHandle::CanCast)
+    if (ObjIsInstanceOfCached(element, arr->GetArrayElementTypeHandle()) == TypeHandle::CanCast)
         return;
     
     FC_INNER_RETURN_VOID(ArrayTypeCheckSlow(element, arr));
index a2d11ff..97264cb 100644 (file)
@@ -25,6 +25,7 @@
 #include "compile.h"
 #endif
 #include "array.h"
+#include "castcache.h"
 
 #ifndef DACCESS_COMPILE
 #ifdef _DEBUG
@@ -353,116 +354,185 @@ BOOL TypeDesc::HasTypeParam()
 
 #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)
@@ -514,169 +584,18 @@ BOOL TypeDesc::CanCastParam(TypeHandle fromParam, TypeHandle toParam, TypeHandle
     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))
index 83ef610..9dca070 100644 (file)
@@ -72,24 +72,23 @@ public:
     // 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
@@ -386,6 +385,9 @@ public:
         return g_pArrayClass;
     }
 
+    BOOL ArrayIsInstanceOf(ArrayTypeDesc* toArrayType, TypeHandlePairList* pVisited);
+    BOOL ArraySupportsBizarreInterface(MethodTable* pInterfaceMT, TypeHandlePairList* pVisited);
+
 #ifdef FEATURE_COMINTEROP
     ComCallWrapperTemplate *GetComCallWrapperTemplate()
     {
index cf21e8e..698b901 100644 (file)
@@ -15,6 +15,8 @@
 #include "typestring.h"
 #include "classloadlevel.h"
 #include "array.h"
+#include "castcache.h"
+
 #ifdef FEATURE_PREJIT 
 #include "zapsig.h"
 #endif
@@ -644,6 +646,7 @@ BOOL TypeHandle::CanCastTo(TypeHandle type, TypeHandlePairList *pVisited)  const
     {
         THROWS;
         GC_TRIGGERS;
+        MODE_ANY;
         INJECT_FAULT(COMPlusThrowOM());
 
         LOADS_TYPE(CLASS_DEPENDENCIES_LOADED);
@@ -651,32 +654,56 @@ BOOL TypeHandle::CanCastTo(TypeHandle type, TypeHandlePairList *pVisited)  const
     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>
 
@@ -781,18 +808,24 @@ TypeHandle TypeHandle::MergeTypeHandlesToCommonParent(TypeHandle ta, TypeHandle
     {
         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);
     }
index 888a428..496057a 100644 (file)
@@ -266,15 +266,15 @@ public:
     // 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
@@ -284,7 +284,7 @@ public:
 
     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