1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 // ==--==
5 //
6 // Shared (non-architecture specific) portions of a mechanism to perform interface dispatch using an alternate
7 // mechanism to VSD that does not require runtime generation of code.
8 //
9 // ============================================================================
10 #include "common.h"
11 #ifdef FEATURE_CACHED_INTERFACE_DISPATCH
12 
13 #include "CommonTypes.h"
14 #include "CommonMacros.h"
15 #include "daccess.h"
16 #include "DebugMacrosExt.h"
17 #include "PalRedhawkCommon.h"
18 #include "PalRedhawk.h"
19 #include "rhassert.h"
20 #include "slist.h"
21 #include "holder.h"
22 #include "Crst.h"
23 #include "RedhawkWarnings.h"
24 #include "TargetPtrs.h"
25 #include "eetype.h"
26 #include "Range.h"
27 #include "allocheap.h"
28 #include "rhbinder.h"
29 #include "ObjectLayout.h"
30 #include "gcrhinterface.h"
31 #include "shash.h"
32 #include "RWLock.h"
33 #include "module.h"
34 #include "RuntimeInstance.h"
35 #include "eetype.inl"
36 
37 #include "CachedInterfaceDispatch.h"
38 
39 // We always allocate cache sizes with a power of 2 number of entries. We have a maximum size we support,
40 // defined below.
41 #define CID_MAX_CACHE_SIZE_LOG2 6
42 #define CID_MAX_CACHE_SIZE      (1 << CID_MAX_CACHE_SIZE_LOG2)
43 
44 //#define FEATURE_CID_STATS 1
45 
46 #ifdef FEATURE_CID_STATS
47 
48 // Some counters used for debugging and profiling the algorithms.
49 extern "C"
50 {
51     UInt32 CID_g_cLoadVirtFunc = 0;
52     UInt32 CID_g_cCacheMisses = 0;
53     UInt32 CID_g_cCacheSizeOverflows = 0;
54     UInt32 CID_g_cCacheOutOfMemory = 0;
55     UInt32 CID_g_cCacheReallocates = 0;
56     UInt32 CID_g_cCacheAllocates = 0;
57     UInt32 CID_g_cCacheDiscards = 0;
58     UInt32 CID_g_cInterfaceDispatches = 0;
59     UInt32 CID_g_cbMemoryAllocated = 0;
60     UInt32 CID_g_rgAllocatesBySize[CID_MAX_CACHE_SIZE_LOG2 + 1] = { 0 };
61 };
62 
63 #define CID_COUNTER_INC(_counter_name) CID_g_c##_counter_name++
64 
65 #else
66 
67 #define CID_COUNTER_INC(_counter_name)
68 
69 #endif // FEATURE_CID_STATS
70 
71 // Helper function for updating two adjacent pointers (which are aligned on a double pointer-sized boundary)
72 // atomically.
73 //
74 // This is used to update interface dispatch cache entries and also the stub/cache pair in
75 // interface dispatch indirection cells. The cases have slightly different semantics: cache entry updates
76 // (fFailOnNonNull == true) require that the existing values in the location are both NULL whereas indirection
77 // cell updates have no such restriction. In both cases we'll try the update once; on failure we'll return the
78 // new value of the second pointer and on success we'll the old value of the second pointer.
79 //
80 // This suits the semantics of both callers. For indirection cell updates the caller needs to know the address
81 // of the cache that can now be scheduled for release and the cache pointer is the second one in the pair. For
82 // cache entry updates the caller only needs a success/failure indication: on success the return value will be
83 // NULL and on failure non-NULL.
UpdatePointerPairAtomically(void * pPairLocation,void * pFirstPointer,void * pSecondPointer,bool fFailOnNonNull)84 static void * UpdatePointerPairAtomically(void * pPairLocation,
85                                           void * pFirstPointer,
86                                           void * pSecondPointer,
87                                           bool fFailOnNonNull)
88 {
89 #if defined(BIT64)
90     // The same comments apply to the AMD64 version. The CompareExchange looks a little different since the
91     // API was refactored in terms of Int64 to avoid creating a 128-bit integer type.
92 
93     Int64 rgComparand[2] = { 0 , 0 };
94     if (!fFailOnNonNull)
95     {
96         rgComparand[0] = *(Int64 volatile *)pPairLocation;
97         rgComparand[1] = *((Int64 volatile *)pPairLocation + 1);
98     }
99 
100     UInt8 bResult = PalInterlockedCompareExchange128((Int64*)pPairLocation, (Int64)pSecondPointer, (Int64)pFirstPointer, rgComparand);
101     if (bResult == 1)
102     {
103         // Success, return old value of second pointer (rgComparand is updated by
104         // PalInterlockedCompareExchange128 with the old pointer values in this case).
105         return (void*)rgComparand[1];
106     }
107 
108     // Failure, return the new second pointer value.
109     return pSecondPointer;
110 #else
111     // Stuff the two pointers into a 64-bit value as the proposed new value for the CompareExchange64 below.
112     Int64 iNewValue = (Int64)((UInt64)(UIntNative)pFirstPointer | ((UInt64)(UIntNative)pSecondPointer << 32));
113 
114     // Read the old value in the location. If fFailOnNonNull is set we just assume this was zero and we'll
115     // fail below if that's not the case.
116     Int64 iOldValue = fFailOnNonNull ? 0 : *(Int64 volatile *)pPairLocation;
117 
118     Int64 iUpdatedOldValue = PalInterlockedCompareExchange64((Int64*)pPairLocation, iNewValue, iOldValue);
119     if (iUpdatedOldValue == iOldValue)
120     {
121         // Successful update. Return the previous value of the second pointer. For cache entry updates
122         // (fFailOnNonNull == true) this is guaranteed to be NULL in this case and the result being being
123         // NULL in the success case is all the caller cares about. For indirection cell updates the second
124         // pointer represents the old cache and the caller needs this data so they can schedule the cache
125         // for deletion once it becomes safe to do so.
126         return (void*)(UInt32)(iOldValue >> 32);
127     }
128 
129     // The update failed due to a racing update to the same location. Return the new value of the second
130     // pointer (either a new cache that lost the race or a non-NULL pointer in the cache entry update case).
131     return pSecondPointer;
132 #endif // BIT64
133 }
134 
135 // Helper method for updating an interface dispatch cache entry atomically. See comments by the usage of
136 // this method for the details of why we need this. If a racing update is detected false is returned and the
137 // update abandoned. This is necessary since it's not safe to update a valid cache entry (one with a non-NULL
138 // m_pInstanceType field) outside of a GC.
UpdateCacheEntryAtomically(InterfaceDispatchCacheEntry * pEntry,EEType * pInstanceType,void * pTargetCode)139 static bool UpdateCacheEntryAtomically(InterfaceDispatchCacheEntry *pEntry,
140                                        EEType * pInstanceType,
141                                        void * pTargetCode)
142 {
143     C_ASSERT(sizeof(InterfaceDispatchCacheEntry) == (sizeof(void*) * 2));
144     C_ASSERT(offsetof(InterfaceDispatchCacheEntry, m_pInstanceType) < offsetof(InterfaceDispatchCacheEntry, m_pTargetCode));
145 
146     return UpdatePointerPairAtomically(pEntry, pInstanceType, pTargetCode, true) == NULL;
147 }
148 
149 // Helper method for updating an interface dispatch indirection cell's stub and cache pointer atomically.
150 // Returns the value of the cache pointer that is not referenced by the cell after this operation. This can be
151 // NULL on the initial cell update, the value of the old cache pointer or the value of the new cache pointer
152 // supplied (in the case where another thread raced with us for the update and won). In any case, if the
153 // returned pointer is non-NULL it represents a cache that should be scheduled for release.
UpdateCellStubAndCache(InterfaceDispatchCell * pCell,void * pStub,UIntNative newCacheValue)154 static InterfaceDispatchCache * UpdateCellStubAndCache(InterfaceDispatchCell * pCell,
155                                                        void * pStub,
156                                                        UIntNative newCacheValue)
157 {
158     C_ASSERT(offsetof(InterfaceDispatchCell, m_pStub) == 0);
159     C_ASSERT(offsetof(InterfaceDispatchCell, m_pCache) == sizeof(void*));
160 
161     UIntNative oldCacheValue = (UIntNative)UpdatePointerPairAtomically(pCell, pStub, (void*)newCacheValue, false);
162 
163     if (InterfaceDispatchCell::IsCache(oldCacheValue))
164     {
165         return (InterfaceDispatchCache *)oldCacheValue;
166     }
167     else
168     {
169         return nullptr;
170     }
171 }
172 
173 //
174 // Cache allocation logic.
175 //
176 // We use the existing AllocHeap mechanism as our base allocator for cache blocks. This is because it can
177 // provide the required 16-byte alignment with no padding or heap header costs. The downside is that there is
178 // no deallocation support (which would be hard to implement without implementing a cache block compaction
179 // scheme, which is certainly possible but not necessarily needed at this point).
180 //
181 // Instead, much like the original VSD algorithm, we keep discarded cache blocks and use them to satisfy new
182 // allocation requests before falling back on AllocHeap.
183 //
184 // We can't re-use discarded cache blocks immediately since there may be code that is still using them.
185 // Instead we link them into a global list and then at the next GC (when no code can hold a reference to these
186 // any more) we can place them on one of several free lists based on their size.
187 //
188 
189 #if defined(_AMD64_) || defined(_ARM64_)
190 
191 // Head of the list of discarded cache blocks that can't be re-used just yet.
192 InterfaceDispatchCache * g_pDiscardedCacheList; // for AMD64 and ARM64, m_pCell is not used and we can link the discarded blocks themselves
193 
194 #else // defined(_AMD64_) || defined(_ARM64_)
195 
196 struct DiscardedCacheBlock
197 {
198     DiscardedCacheBlock *       m_pNext;        // for x86 and ARM, we are short of registers, thus need the m_pCell back pointers
199     InterfaceDispatchCache *    m_pCache;       // and thus need this auxiliary list
200 };
201 
202 // Head of the list of discarded cache blocks that can't be re-used just yet.
203 static DiscardedCacheBlock * g_pDiscardedCacheList = NULL;
204 
205 // Free list of DiscardedCacheBlock items
206 static DiscardedCacheBlock * g_pDiscardedCacheFree = NULL;
207 
208 #endif // defined(_AMD64_) || defined(_ARM64_)
209 
210 // Free lists for each cache size up to the maximum. We allocate from these in preference to new memory.
211 static InterfaceDispatchCache * g_rgFreeLists[CID_MAX_CACHE_SIZE_LOG2 + 1];
212 
213 // Lock protecting both g_pDiscardedCacheList and g_rgFreeLists. We don't use the OS SLIST support here since
214 // it imposes too much space overhead on list entries on 64-bit (each is actually 16 bytes).
215 static CrstStatic g_sListLock;
216 
217 // The base memory allocator.
218 static AllocHeap * g_pAllocHeap = NULL;
219 
220 // Each cache size has an associated stub used to perform lookup over that cache.
221 extern "C" void (*RhpInterfaceDispatch1)();
222 extern "C" void (*RhpInterfaceDispatch2)();
223 extern "C" void (*RhpInterfaceDispatch4)();
224 extern "C" void (*RhpInterfaceDispatch8)();
225 extern "C" void (*RhpInterfaceDispatch16)();
226 extern "C" void (*RhpInterfaceDispatch32)();
227 extern "C" void (*RhpInterfaceDispatch64)();
228 
229 extern "C" void (*RhpVTableOffsetDispatch)();
230 
231 typedef void (*InterfaceDispatchStub)();
232 
233 static void * g_rgDispatchStubs[CID_MAX_CACHE_SIZE_LOG2 + 1] = {
234     &RhpInterfaceDispatch1,
235     &RhpInterfaceDispatch2,
236     &RhpInterfaceDispatch4,
237     &RhpInterfaceDispatch8,
238     &RhpInterfaceDispatch16,
239     &RhpInterfaceDispatch32,
240     &RhpInterfaceDispatch64,
241 };
242 
243 // Map a cache size into a linear index.
CacheSizeToIndex(UInt32 cCacheEntries)244 static UInt32 CacheSizeToIndex(UInt32 cCacheEntries)
245 {
246     switch (cCacheEntries)
247     {
248     case 1:
249         return 0;
250     case 2:
251         return 1;
252     case 4:
253         return 2;
254     case 8:
255         return 3;
256     case 16:
257         return 4;
258     case 32:
259         return 5;
260     case 64:
261         return 6;
262     default:
263         UNREACHABLE();
264     }
265 }
266 
267 // Allocates and initializes new cache of the given size. If given a previous version of the cache (guaranteed
268 // to be smaller) it will also pre-populate the new cache with the contents of the old. Additionally the
269 // address of the interface dispatch stub associated with this size of cache is returned.
AllocateCache(UInt32 cCacheEntries,InterfaceDispatchCache * pExistingCache,const DispatchCellInfo * pNewCellInfo,void ** ppStub)270 static UIntNative AllocateCache(UInt32 cCacheEntries, InterfaceDispatchCache * pExistingCache, const DispatchCellInfo *pNewCellInfo, void ** ppStub)
271 {
272     if (pNewCellInfo->CellType == DispatchCellType::VTableOffset)
273     {
274         ASSERT(pNewCellInfo->VTableOffset < InterfaceDispatchCell::IDC_MaxVTableOffsetPlusOne);
275         *ppStub = &RhpVTableOffsetDispatch;
276         ASSERT(!InterfaceDispatchCell::IsCache(pNewCellInfo->VTableOffset));
277         return pNewCellInfo->VTableOffset;
278     }
279 
280     ASSERT((cCacheEntries >= 1) && (cCacheEntries <= CID_MAX_CACHE_SIZE));
281     ASSERT((pExistingCache == NULL) || (pExistingCache->m_cEntries < cCacheEntries));
282 
283     InterfaceDispatchCache * pCache = NULL;
284 
285     // Transform cache size back into a linear index.
286     UInt32 idxCacheSize = CacheSizeToIndex(cCacheEntries);
287 
288     // Attempt to allocate the head of the free list of the correct cache size.
289     if (g_rgFreeLists[idxCacheSize] != NULL)
290     {
291         CrstHolder lh(&g_sListLock);
292 
293         pCache = g_rgFreeLists[idxCacheSize];
294         if (pCache != NULL)
295         {
296             g_rgFreeLists[idxCacheSize] = pCache->m_pNextFree;
297             CID_COUNTER_INC(CacheReallocates);
298         }
299     }
300 
301     if (pCache == NULL)
302     {
303         // No luck with the free list, allocate the cache from via the AllocHeap.
304         pCache = (InterfaceDispatchCache*)g_pAllocHeap->AllocAligned(sizeof(InterfaceDispatchCache) +
305                                                                      (sizeof(InterfaceDispatchCacheEntry) * cCacheEntries),
306                                                                      sizeof(void*) * 2);
307         if (pCache == NULL)
308             return NULL;
309 
310         CID_COUNTER_INC(CacheAllocates);
311 #ifdef FEATURE_CID_STATS
312         CID_g_cbMemoryAllocated += sizeof(InterfaceDispatchCacheEntry) * cCacheEntries;
313         CID_g_rgAllocatesBySize[idxCacheSize]++;
314 #endif
315     }
316 
317     // We have a cache block, now initialize it.
318     pCache->m_pNextFree = NULL;
319     pCache->m_cEntries = cCacheEntries;
320     pCache->m_cacheHeader.Initialize(pNewCellInfo);
321 
322     // Copy over entries from previous version of the cache (if any) and zero the rest.
323     if (pExistingCache)
324     {
325         memcpy(pCache->m_rgEntries,
326                pExistingCache->m_rgEntries,
327                sizeof(InterfaceDispatchCacheEntry) * pExistingCache->m_cEntries);
328         memset(&pCache->m_rgEntries[pExistingCache->m_cEntries],
329                0,
330                (cCacheEntries - pExistingCache->m_cEntries) * sizeof(InterfaceDispatchCacheEntry));
331     }
332     else
333     {
334         memset(pCache->m_rgEntries,
335                0,
336                cCacheEntries * sizeof(InterfaceDispatchCacheEntry));
337     }
338 
339     // Pass back the stub the corresponds to this cache size.
340     *ppStub = g_rgDispatchStubs[idxCacheSize];
341 
342     return (UIntNative)pCache;
343 }
344 
345 // Discards a cache by adding it to a list of caches that may still be in use but will be made available for
346 // re-allocation at the next GC.
DiscardCache(InterfaceDispatchCache * pCache)347 static void DiscardCache(InterfaceDispatchCache * pCache)
348 {
349     CID_COUNTER_INC(CacheDiscards);
350 
351     CrstHolder lh(&g_sListLock);
352 
353 #if defined(_AMD64_) || defined(_ARM64_)
354 
355     // on AMD64 and ARM64, we can thread the list through the blocks directly
356     pCache->m_pNextFree = g_pDiscardedCacheList;
357     g_pDiscardedCacheList = pCache;
358 
359 #else // defined(_AMD64_) || defined(_ARM64_)
360 
361     // on other architectures, we cannot overwrite pCache->m_pNextFree yet
362     // because it shares storage with m_pCell which may still be used as a back
363     // pointer to the dispatch cell.
364 
365     // instead, allocate an auxiliary node (with its own auxiliary free list)
366     DiscardedCacheBlock * pDiscardedCacheBlock = g_pDiscardedCacheFree;
367     if (pDiscardedCacheBlock != NULL)
368         g_pDiscardedCacheFree = pDiscardedCacheBlock->m_pNext;
369     else
370         pDiscardedCacheBlock = (DiscardedCacheBlock *)g_pAllocHeap->Alloc(sizeof(DiscardedCacheBlock));
371 
372     if (pDiscardedCacheBlock != NULL) // if we did NOT get the memory, we leak the discarded block
373     {
374         pDiscardedCacheBlock->m_pNext = g_pDiscardedCacheList;
375         pDiscardedCacheBlock->m_pCache = pCache;
376 
377         g_pDiscardedCacheList = pDiscardedCacheBlock;
378     }
379 #endif // defined(_AMD64_) || defined(_ARM64_)
380 }
381 
382 // Called during a GC to empty the list of discarded caches (which we can now guarantee aren't being accessed)
383 // and sort the results into the free lists we maintain for each cache size.
ReclaimUnusedInterfaceDispatchCaches()384 void ReclaimUnusedInterfaceDispatchCaches()
385 {
386     // No need for any locks, we're not racing with any other threads any more.
387 
388     // Walk the list of discarded caches.
389 #if defined(_AMD64_) || defined(_ARM64_)
390 
391     // on AMD64, this is threaded directly through the cache blocks
392     InterfaceDispatchCache * pCache = g_pDiscardedCacheList;
393     while (pCache)
394     {
395         InterfaceDispatchCache * pNextCache = pCache->m_pNextFree;
396 
397         // Transform cache size back into a linear index.
398         UInt32 idxCacheSize = CacheSizeToIndex(pCache->m_cEntries);
399 
400         // Insert the cache onto the head of the correct free list.
401         pCache->m_pNextFree = g_rgFreeLists[idxCacheSize];
402         g_rgFreeLists[idxCacheSize] = pCache;
403 
404         pCache = pNextCache;
405     }
406 
407 #else // defined(_AMD64_) || defined(_ARM64_)
408 
409     // on other architectures, we use an auxiliary list instead
410     DiscardedCacheBlock * pDiscardedCacheBlock = g_pDiscardedCacheList;
411     while (pDiscardedCacheBlock)
412     {
413         InterfaceDispatchCache * pCache = pDiscardedCacheBlock->m_pCache;
414 
415         // Transform cache size back into a linear index.
416         UInt32 idxCacheSize = CacheSizeToIndex(pCache->m_cEntries);
417 
418         // Insert the cache onto the head of the correct free list.
419         pCache->m_pNextFree = g_rgFreeLists[idxCacheSize];
420         g_rgFreeLists[idxCacheSize] = pCache;
421 
422         // Insert the container to its own free list
423         DiscardedCacheBlock * pNextDiscardedCacheBlock = pDiscardedCacheBlock->m_pNext;
424         pDiscardedCacheBlock->m_pNext = g_pDiscardedCacheFree;
425         g_pDiscardedCacheFree = pDiscardedCacheBlock;
426         pDiscardedCacheBlock = pNextDiscardedCacheBlock;
427     }
428 
429 #endif // defined(_AMD64_) || defined(_ARM64_)
430 
431     // We processed all the discarded entries, so we can simply NULL the list head.
432     g_pDiscardedCacheList = NULL;
433 }
434 
435 // One time initialization of interface dispatch.
InitializeInterfaceDispatch()436 bool InitializeInterfaceDispatch()
437 {
438     g_pAllocHeap = new AllocHeap();
439     if (g_pAllocHeap == NULL)
440         return false;
441 
442     if (!g_pAllocHeap->Init())
443         return false;
444 
445     g_sListLock.Init(CrstInterfaceDispatchGlobalLists, CRST_DEFAULT);
446 
447     return true;
448 }
449 
450 COOP_PINVOKE_HELPER(PTR_Code, RhpUpdateDispatchCellCache, (InterfaceDispatchCell * pCell, PTR_Code pTargetCode, EEType* pInstanceType, DispatchCellInfo *pNewCellInfo))
451 {
452     // Attempt to update the cache with this new mapping (if we have any cache at all, the initial state
453     // is none).
454     InterfaceDispatchCache * pCache = (InterfaceDispatchCache*)pCell->GetCache();
455     UInt32 cOldCacheEntries = 0;
456     if (pCache != NULL)
457     {
458         InterfaceDispatchCacheEntry * pCacheEntry = pCache->m_rgEntries;
459         for (UInt32 i = 0; i < pCache->m_cEntries; i++, pCacheEntry++)
460         {
461             if (pCacheEntry->m_pInstanceType == NULL)
462             {
463                 if (UpdateCacheEntryAtomically(pCacheEntry, pInstanceType, pTargetCode))
464                     return (PTR_Code)pTargetCode;
465             }
466         }
467 
468         cOldCacheEntries = pCache->m_cEntries;
469     }
470 
471     // Failed to update an existing cache, we need to allocate a new cache. The old one, if any, might
472     // still be in use so we can't simply reclaim it. Instead we keep it around until the next GC at which
473     // point we know no code is holding a reference to it. Particular cache sizes are associated with a
474     // (globally shared) stub which implicitly knows the size of the cache.
475 
476     if (cOldCacheEntries == CID_MAX_CACHE_SIZE)
477     {
478         // We already reached the maximum cache size we wish to allocate. For now don't attempt to cache
479         // the mapping we just did: there's no safe way to update the existing cache right now if it
480         // doesn't have an empty entries. There are schemes that would let us do this at the next GC point
481         // but it's not clear whether we should do this or re-tune the cache max size, we need to measure
482         // this.
483         CID_COUNTER_INC(CacheSizeOverflows);
484         return (PTR_Code)pTargetCode;
485     }
486 
487     UInt32 cNewCacheEntries = cOldCacheEntries ? cOldCacheEntries * 2 : 1;
488     void *pStub;
489     UIntNative newCacheValue = AllocateCache(cNewCacheEntries, pCache, pNewCellInfo, &pStub);
490     if (newCacheValue == 0)
491     {
492         CID_COUNTER_INC(CacheOutOfMemory);
493         return (PTR_Code)pTargetCode;
494     }
495 
496     if (InterfaceDispatchCell::IsCache(newCacheValue))
497     {
498         pCache = (InterfaceDispatchCache*)newCacheValue;
499 #if !defined(_AMD64_) && !defined(_ARM64_)
500         // Set back pointer to interface dispatch cell for non-AMD64 and non-ARM64
501         // for AMD64 and ARM64, we have enough registers to make this trick unnecessary
502         pCache->m_pCell = pCell;
503 #endif // !defined(_AMD64_) && !defined(_ARM64_)
504 
505         // Add entry to the first unused slot.
506         InterfaceDispatchCacheEntry * pCacheEntry = &pCache->m_rgEntries[cOldCacheEntries];
507         pCacheEntry->m_pInstanceType = pInstanceType;
508         pCacheEntry->m_pTargetCode = pTargetCode;
509     }
510 
511     // Publish the new cache by atomically updating both the cache and stub pointers in the indirection
512     // cell. This returns us a cache to discard which may be NULL (no previous cache), the previous cache
513     // value or the cache we just allocated (another thread peformed an update first).
514     InterfaceDispatchCache * pDiscardedCache = UpdateCellStubAndCache(pCell, pStub, newCacheValue);
515     if (pDiscardedCache)
516         DiscardCache(pDiscardedCache);
517 
518     return (PTR_Code)pTargetCode;
519 }
520 
521 COOP_PINVOKE_HELPER(PTR_Code, RhpSearchDispatchCellCache, (InterfaceDispatchCell * pCell, EEType* pInstanceType))
522 {
523     // This function must be implemented in native code so that we do not take a GC while walking the cache
524     InterfaceDispatchCache * pCache = (InterfaceDispatchCache*)pCell->GetCache();
525     if (pCache != NULL)
526     {
527         InterfaceDispatchCacheEntry * pCacheEntry = pCache->m_rgEntries;
528         for (UInt32 i = 0; i < pCache->m_cEntries; i++, pCacheEntry++)
529             if (pCacheEntry->m_pInstanceType == pInstanceType)
530                 return (PTR_Code)pCacheEntry->m_pTargetCode;
531     }
532 
533     return nullptr;
534 }
535 
536 // Given a dispatch cell, get the type and slot associated with it. This function MUST be implemented
537 // in cooperative native code, as the m_pCache field on the cell is unsafe to access from managed
538 // code due to its use of the GC state as a lock, and as lifetime control
539 COOP_PINVOKE_HELPER(void, RhpGetDispatchCellInfo, (InterfaceDispatchCell * pCell, DispatchCellInfo* pDispatchCellInfo))
540 {
541     *pDispatchCellInfo = pCell->GetDispatchCellInfo();
542 }
543 
544 EXTERN_C DECLSPEC_THREAD void* t_TLS_DispatchCell = nullptr;
545 
546 COOP_PINVOKE_HELPER(void, RhpSetTLSDispatchCell, (void *dispatchCell))
547 {
548     t_TLS_DispatchCell = dispatchCell;
549 }
550 
551 extern "C" void(*RhpTailCallTLSDispatchCell)();
552 COOP_PINVOKE_HELPER(void*, RhpGetTailCallTLSDispatchCell, ())
553 {
554     return &RhpTailCallTLSDispatchCell;
555 }
556 
557 extern "C" void(*RhpCastableObjectDispatchHelper)();
558 COOP_PINVOKE_HELPER(void*, RhpGetCastableObjectDispatchHelper, ())
559 {
560     return &RhpCastableObjectDispatchHelper;
561 }
562 
563 extern "C" void(*RhpCastableObjectDispatchHelper_TailCalled)();
564 COOP_PINVOKE_HELPER(void*, RhpGetCastableObjectDispatchHelper_TailCalled, ())
565 {
566     return &RhpCastableObjectDispatchHelper_TailCalled;
567 }
568 
569 extern "C" void(*RhpCastableObjectDispatch_CommonStub)();
570 COOP_PINVOKE_HELPER(void*, RhpGetCastableObjectDispatch_CommonStub, ())
571 {
572     return &RhpCastableObjectDispatch_CommonStub;
573 }
574 
575 #endif // FEATURE_CACHED_INTERFACE_DISPATCH
576