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