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// File: VirtualCallStub.h
6//
7
8
9
10//
11// See code:VirtualCallStubManager for details
12//
13// ============================================================================
14
15#ifndef _VIRTUAL_CALL_STUB_H
16#define _VIRTUAL_CALL_STUB_H
17
18#ifndef CROSSGEN_COMPILE
19
20#define CHAIN_LOOKUP
21
22#if defined(_TARGET_X86_)
23// If this is uncommented, leaves a file "StubLog_<pid>.log" with statistics on the behavior
24// of stub-based interface dispatch.
25//#define STUB_LOGGING
26#endif
27
28#include "stubmgr.h"
29
30/////////////////////////////////////////////////////////////////////////////////////
31// Forward class declarations
32class FastTable;
33class BucketTable;
34class Entry;
35class Prober;
36class VirtualCallStubManager;
37class VirtualCallStubManagerManager;
38struct LookupHolder;
39struct DispatchHolder;
40struct ResolveHolder;
41struct VTableCallHolder;
42
43/////////////////////////////////////////////////////////////////////////////////////
44// Forward function declarations
45extern "C" void InContextTPQuickDispatchAsmStub();
46
47extern "C" PCODE STDCALL StubDispatchFixupWorker(TransitionBlock * pTransitionBlock,
48 TADDR siteAddrForRegisterIndirect,
49 DWORD sectionIndex,
50 Module * pModule);
51
52extern "C" PCODE STDCALL VSD_ResolveWorker(TransitionBlock * pTransitionBlock,
53 TADDR siteAddrForRegisterIndirect,
54 size_t token
55#ifndef _TARGET_X86_
56 , UINT_PTR flags
57#endif
58 );
59
60
61/////////////////////////////////////////////////////////////////////////////////////
62#if defined(_TARGET_X86_) || defined(_TARGET_AMD64_)
63typedef INT32 DISPL;
64#endif
65
66/////////////////////////////////////////////////////////////////////////////////////
67// Represents the struct that is added to the resolve cache
68// NOTE: If you change the layout of this struct, you'll need to update various
69// ASM helpers in VirtualCallStubCpu that rely on offsets of members.
70//
71struct ResolveCacheElem
72{
73 void *pMT;
74 size_t token; // DispatchToken
75 void *target;
76
77 // These are used for chaining
78 ResolveCacheElem *pNext;
79 ResolveCacheElem *Next()
80 { LIMITED_METHOD_CONTRACT; return VolatileLoad(&pNext); }
81
82#ifdef _DEBUG
83 UINT16 debug_hash;
84 UINT16 debug_index;
85#endif // _DEBUG
86
87 BOOL Equals(size_t token, void *pMT)
88 { LIMITED_METHOD_CONTRACT; return (this->pMT == pMT && this->token == token); }
89
90 BOOL Equals(ResolveCacheElem *pElem)
91 { WRAPPER_NO_CONTRACT; return Equals(pElem->token, pElem->pMT); }
92
93};
94
95enum
96{
97 e_resolveCacheElem_sizeof_mt = sizeof(void *),
98 e_resolveCacheElem_sizeof_token = sizeof(size_t),
99 e_resolveCacheElem_sizeof_target = sizeof(void *),
100 e_resolveCacheElem_sizeof_next = sizeof(ResolveCacheElem *),
101
102 e_resolveCacheElem_offset_mt = 0,
103 e_resolveCacheElem_offset_token = e_resolveCacheElem_offset_mt + e_resolveCacheElem_sizeof_mt,
104 e_resolveCacheElem_offset_target = e_resolveCacheElem_offset_token + e_resolveCacheElem_sizeof_token,
105 e_resolveCacheElem_offset_next = e_resolveCacheElem_offset_target + e_resolveCacheElem_sizeof_target,
106};
107
108/////////////////////////////////////////////////////////////////////////////////////
109// A utility class to help manipulate a call site
110struct StubCallSite
111{
112 friend class VirtualCallStubManager;
113
114private:
115
116 // On x86 are four possible kinds of callsites when you take into account all features
117 // Relative: direct call, e.g. "call addr". Not used currently.
118 // RelativeIndirect (JmpRel): indirect call through a relative address, e.g. "call [addr]"
119 // RegisterIndirect: indirect call through a register, e.g. "call [eax]"
120 // DelegateCallSite: anything else, tail called through a register by shuffle thunk, e.g. "jmp [eax]"
121 //
122 // On all other platforms we always use an indirect call through an indirection cell
123 // In these cases all calls are made by the platform equivalent of "call [addr]".
124 //
125 // DelegateCallSite are particular in that they can come in a variety of forms:
126 // a direct delegate call has a sequence defined by the jit but a multicast or secure delegate
127 // are defined in a stub and have a different shape
128 //
129 PTR_PCODE m_siteAddr; // Stores the address of an indirection cell
130 PCODE m_returnAddr;
131
132public:
133
134#if defined(_TARGET_X86_)
135 StubCallSite(TADDR siteAddrForRegisterIndirect, PCODE returnAddr);
136
137 PCODE GetCallerAddress();
138#else // !defined(_TARGET_X86_)
139 // On platforms where we always use an indirection cell things
140 // are much simpler - the siteAddr always stores a pointer to a
141 // value that in turn points to the indirection cell.
142
143 StubCallSite(TADDR siteAddr, PCODE returnAddr)
144 { LIMITED_METHOD_CONTRACT; m_siteAddr = dac_cast<PTR_PCODE>(siteAddr); m_returnAddr = returnAddr; }
145
146 PCODE GetCallerAddress() { LIMITED_METHOD_CONTRACT; return m_returnAddr; }
147#endif // !defined(_TARGET_X86_)
148
149 PCODE GetSiteTarget() { WRAPPER_NO_CONTRACT; return *(GetIndirectCell()); }
150 void SetSiteTarget(PCODE newTarget);
151 PTR_PCODE GetIndirectCell() { LIMITED_METHOD_CONTRACT; return dac_cast<PTR_PCODE>(m_siteAddr); }
152 PTR_PCODE * GetIndirectCellAddress() { LIMITED_METHOD_CONTRACT; return &m_siteAddr; }
153
154 PCODE GetReturnAddress() { LIMITED_METHOD_CONTRACT; return m_returnAddr; }
155};
156
157#ifdef FEATURE_PREJIT
158extern "C" void StubDispatchFixupStub(); // for lazy fixup of ngen call sites
159#endif
160
161// These are the assembly language entry points that the stubs use when they want to go into the EE
162
163extern "C" void ResolveWorkerAsmStub(); // resolve a token and transfer control to that method
164extern "C" void ResolveWorkerChainLookupAsmStub(); // for chaining of entries in the cache
165
166#ifdef _TARGET_X86_
167extern "C" void BackPatchWorkerAsmStub(); // backpatch a call site to point to a different stub
168#ifdef FEATURE_PAL
169extern "C" void BackPatchWorkerStaticStub(PCODE returnAddr, TADDR siteAddrForRegisterIndirect);
170#endif // FEATURE_PAL
171#endif // _TARGET_X86_
172
173
174typedef VPTR(class VirtualCallStubManager) PTR_VirtualCallStubManager;
175
176// VirtualCallStubManager is the heart of the stub dispatch logic. See the book of the runtime entry
177//
178// file:../../doc/BookOfTheRuntime/ClassLoader/VirtualStubDispatchDesign.doc
179//
180// The basic idea is that a call to an interface (it could also be used for virtual calls in general, but we
181// do not do this), is simply the code
182//
183// call [DispatchCell]
184//
185// Where we make sure 'DispatchCell' points at stubs that will do the right thing. DispatchCell is writable
186// so we can udpate the code over time. There are three basic types of stubs that the dispatch cell can point
187// to.
188// * Lookup: The intial stub that has no 'fast path' and simply pushes a ID for interface being called
189// and calls into the runtime at code:VirtualCallStubManager.ResolveWorkerStatic.
190// * Dispatch: Lookup stubs are patched to this stub which has a fast path that checks for a particular
191// Method Table and if that fails jumps to code that
192// * Decrements a 'missCount' (starts out as code:STUB_MISS_COUNT_VALUE). If this count goes to zero
193// code:VirtualCallStubManager.BackPatchWorkerStatic is called, morphs it into a resolve stub
194// (however since this decrementing logic is SHARED among all dispatch stubs, it may take
195// multiples of code:STUB_MISS_COUNT_VALUE if mulitple call sites are actively polymorphic (this
196// seems unlikley).
197// * Calls a resolve stub (Whenever a dispatch stub is created, it always has a cooresponding resolve
198// stub (but the resolve stubs are shared among many dispatch stubs).
199// * Resolve: see code:ResolveStub. This looks up the Method table in a process wide cache (see
200// code:ResolveCacheElem, and if found, jumps to it. This code path is about 17 instructions long (so
201// pretty fast, but certainly much slower than a normal call). If the method table is not found in
202// the cache, it calls into the runtime code:VirtualCallStubManager.ResolveWorkerStatic, which
203// populates it.
204// So the general progression is call site's cells
205// * start out life pointing to a lookup stub
206// * On first call they get updated into a dispatch stub. When this misses, it calls a resolve stub,
207// which populates a resovle stub's cache, but does not update the call site' cell (thus it is still
208// pointing at the dispatch cell.
209// * After code:STUB_MISS_COUNT_VALUE misses, we update the call site's cell to point directly at the
210// resolve stub (thus avoiding the overhead of the quick check that always seems to be failing and
211// the miss count update).
212//
213// QUESTION: What is the lifetimes of the various stubs and hash table entries?
214//
215// QUESTION: There does not seem to be any logic that will change a call site's cell once it becomes a
216// Resolve stub. Thus once a particular call site becomes a Resolve stub we live with the Resolve stub's
217// (in)efficiency forever.
218//
219// see code:#StubDispatchNotes for more
220class VirtualCallStubManager : public StubManager
221{
222 friend class ClrDataAccess;
223 friend class VirtualCallStubManagerManager;
224 friend class VirtualCallStubManagerIterator;
225
226 VPTR_VTABLE_CLASS(VirtualCallStubManager, StubManager)
227
228public:
229#ifdef _DEBUG
230 virtual const char * DbgGetName() { LIMITED_METHOD_CONTRACT; return "VirtualCallStubManager"; }
231#endif
232
233 // The reason for our existence, return a callstub for type id and slot number
234 // where type id = 0 for the class contract (i.e. a virtual call), and type id > 0 for an
235 // interface invoke where the id indicates which interface it is.
236 //
237 // The function is idempotent, i.e.
238 // you'll get the same callstub twice if you call it with identical inputs.
239 PCODE GetCallStub(TypeHandle ownerType, MethodDesc *pMD);
240 PCODE GetCallStub(TypeHandle ownerType, DWORD slot);
241
242 // Stubs for vtable-based virtual calls with no lookups
243 PCODE GetVTableCallStub(DWORD slot);
244
245 // Generate an fresh indirection cell.
246 BYTE* GenerateStubIndirection(PCODE stub, BOOL fUseRecycledCell = FALSE);
247
248 // Set up static data structures - called during EEStartup
249 static void InitStatic();
250 static void UninitStatic();
251
252 // Per instance initialization - called during AppDomain::Init and ::Uninit and for collectible loader allocators
253 void Init(BaseDomain* pDomain, LoaderAllocator *pLoaderAllocator);
254 void Uninit();
255
256 //@TODO: the logging should be tied into the VMs normal loggin mechanisms,
257 //@TODO: for now we just always write a short log file called "StubLog_<pid>.log"
258 static void StartupLogging();
259 static void LoggingDump();
260 static void FinishLogging();
261
262 static void ResetCache();
263
264 // Reclaim/rearrange any structures that can only be done during a gc sync point.
265 // This is the mechanism we are using to avoid synchronization of alot of our
266 // cache and hash table accesses. We are requiring that during a gc sync point we are not
267 // executing any stub code at all, hence at this time we are serialized on a single thread (gc)
268 // and no other thread is accessing the data structures.
269 static void ReclaimAll();
270 void Reclaim();
271
272#ifndef DACCESS_COMPILE
273 VirtualCallStubManager()
274 : StubManager(),
275 lookup_rangeList(),
276 resolve_rangeList(),
277 dispatch_rangeList(),
278 cache_entry_rangeList(),
279 vtable_rangeList(),
280 parentDomain(NULL),
281 isCollectible(false),
282 m_initialReservedMemForHeaps(NULL),
283 m_FreeIndCellList(NULL),
284 m_RecycledIndCellList(NULL),
285 indcell_heap(NULL),
286 cache_entry_heap(NULL),
287 lookup_heap(NULL),
288 dispatch_heap(NULL),
289 resolve_heap(NULL),
290#ifdef _TARGET_AMD64_
291 m_fShouldAllocateLongJumpDispatchStubs(FALSE),
292#endif
293 lookups(NULL),
294 cache_entries(NULL),
295 dispatchers(NULL),
296 resolvers(NULL),
297 m_counters(NULL),
298 m_cur_counter_block(NULL),
299 m_cur_counter_block_for_reclaim(NULL),
300 m_cur_counter_block_for_reclaim_index(NULL),
301 m_pNext(NULL)
302 {
303 LIMITED_METHOD_CONTRACT;
304 ZeroMemory(&stats, sizeof(stats));
305 }
306
307 ~VirtualCallStubManager();
308#endif // !DACCESS_COMPILE
309
310
311 enum StubKind {
312 SK_UNKNOWN,
313 SK_LOOKUP, // Lookup Stubs are SLOW stubs that simply call into the runtime to do all work.
314 SK_DISPATCH, // Dispatch Stubs have a fast check for one type otherwise jumps to runtime. Works for monomorphic sites
315 SK_RESOLVE, // Resolve Stubs do a hash lookup before fallling back to the runtime. Works for polymorphic sites.
316 SK_VTABLECALL, // Stub that jumps to a target method using vtable-based indirections. Works for non-interface calls.
317 SK_BREAKPOINT
318 };
319
320 // peek at the assembly code and predict which kind of a stub we have
321 StubKind predictStubKind(PCODE stubStartAddress);
322
323 /* know thine own stubs. It is possible that when multiple
324 virtualcallstub managers are built that these may need to become
325 non-static, and the callers modified accordingly */
326 StubKind getStubKind(PCODE stubStartAddress, BOOL usePredictStubKind = TRUE)
327 {
328 WRAPPER_NO_CONTRACT;
329 SUPPORTS_DAC;
330
331 // This method can called with stubStartAddress==NULL, e.g. when handling null reference exceptions
332 // caused by IP=0. Early out for this case to avoid confusing handled access violations inside predictStubKind.
333 if (PCODEToPINSTR(stubStartAddress) == NULL)
334 return SK_UNKNOWN;
335
336 // Rather than calling IsInRange(stubStartAddress) for each possible stub kind
337 // we can peek at the assembly code and predict which kind of a stub we have
338 StubKind predictedKind = (usePredictStubKind) ? predictStubKind(stubStartAddress) : SK_UNKNOWN;
339
340 if (predictedKind == SK_DISPATCH)
341 {
342 if (isDispatchingStub(stubStartAddress))
343 return SK_DISPATCH;
344 }
345 else if (predictedKind == SK_LOOKUP)
346 {
347 if (isLookupStub(stubStartAddress))
348 return SK_LOOKUP;
349 }
350 else if (predictedKind == SK_RESOLVE)
351 {
352 if (isResolvingStub(stubStartAddress))
353 return SK_RESOLVE;
354 }
355 else if (predictedKind == SK_VTABLECALL)
356 {
357 if (isVTableCallStub(stubStartAddress))
358 return SK_VTABLECALL;
359 }
360
361 // This is the slow case. If the predict returned SK_UNKNOWN, SK_BREAKPOINT,
362 // or the predict was found to be incorrect when checked against the RangeLists
363 // (isXXXStub), then we'll check each stub heap in sequence.
364 if (isDispatchingStub(stubStartAddress))
365 return SK_DISPATCH;
366 else if (isLookupStub(stubStartAddress))
367 return SK_LOOKUP;
368 else if (isResolvingStub(stubStartAddress))
369 return SK_RESOLVE;
370 else if (isVTableCallStub(stubStartAddress))
371 return SK_VTABLECALL;
372
373 return SK_UNKNOWN;
374 }
375
376 inline BOOL isStub(PCODE stubStartAddress)
377 {
378 WRAPPER_NO_CONTRACT;
379 SUPPORTS_DAC;
380
381 return (getStubKind(stubStartAddress) != SK_UNKNOWN);
382 }
383
384 BOOL isDispatchingStub(PCODE stubStartAddress)
385 {
386 WRAPPER_NO_CONTRACT;
387 SUPPORTS_DAC;
388
389 return GetDispatchRangeList()->IsInRange(stubStartAddress);
390 }
391
392 BOOL isResolvingStub(PCODE stubStartAddress)
393 {
394 WRAPPER_NO_CONTRACT;
395 SUPPORTS_DAC;
396
397 return GetResolveRangeList()->IsInRange(stubStartAddress);
398 }
399
400 BOOL isLookupStub(PCODE stubStartAddress)
401 {
402 WRAPPER_NO_CONTRACT;
403 SUPPORTS_DAC;
404
405 return GetLookupRangeList()->IsInRange(stubStartAddress);
406 }
407
408 BOOL isVTableCallStub(PCODE stubStartAddress)
409 {
410 WRAPPER_NO_CONTRACT;
411 SUPPORTS_DAC;
412
413 return GetVTableCallRangeList()->IsInRange(stubStartAddress);
414 }
415
416 static BOOL isDispatchingStubStatic(PCODE addr)
417 {
418 WRAPPER_NO_CONTRACT;
419 StubKind stubKind;
420 FindStubManager(addr, &stubKind);
421 return stubKind == SK_DISPATCH;
422 }
423
424 static BOOL isResolvingStubStatic(PCODE addr)
425 {
426 WRAPPER_NO_CONTRACT;
427 StubKind stubKind;
428 FindStubManager(addr, &stubKind);
429 return stubKind == SK_RESOLVE;
430 }
431
432 static BOOL isLookupStubStatic(PCODE addr)
433 {
434 WRAPPER_NO_CONTRACT;
435 StubKind stubKind;
436 FindStubManager(addr, &stubKind);
437 return stubKind == SK_LOOKUP;
438 }
439
440 static BOOL isVtableCallStubStatic(PCODE addr)
441 {
442 WRAPPER_NO_CONTRACT;
443 StubKind stubKind;
444 FindStubManager(addr, &stubKind);
445 return stubKind == SK_VTABLECALL;
446 }
447
448 //use range lists to track the chunks of memory that are part of each heap
449 LockedRangeList lookup_rangeList;
450 LockedRangeList resolve_rangeList;
451 LockedRangeList dispatch_rangeList;
452 LockedRangeList cache_entry_rangeList;
453 LockedRangeList vtable_rangeList;
454
455 // Get dac-ized pointers to rangelist.
456 RangeList* GetLookupRangeList()
457 {
458 SUPPORTS_DAC;
459
460 TADDR addr = PTR_HOST_MEMBER_TADDR(VirtualCallStubManager, this, lookup_rangeList);
461 return PTR_RangeList(addr);
462 }
463 RangeList* GetResolveRangeList()
464 {
465 SUPPORTS_DAC;
466
467 TADDR addr = PTR_HOST_MEMBER_TADDR(VirtualCallStubManager, this, resolve_rangeList);
468 return PTR_RangeList(addr);
469 }
470 RangeList* GetDispatchRangeList()
471 {
472 SUPPORTS_DAC;
473
474 TADDR addr = PTR_HOST_MEMBER_TADDR(VirtualCallStubManager, this, dispatch_rangeList);
475 return PTR_RangeList(addr);
476 }
477 RangeList* GetCacheEntryRangeList()
478 {
479 SUPPORTS_DAC;
480 TADDR addr = PTR_HOST_MEMBER_TADDR(VirtualCallStubManager, this, cache_entry_rangeList);
481 return PTR_RangeList(addr);
482 }
483 RangeList* GetVTableCallRangeList()
484 {
485 SUPPORTS_DAC;
486 TADDR addr = PTR_HOST_MEMBER_TADDR(VirtualCallStubManager, this, vtable_rangeList);
487 return PTR_RangeList(addr);
488 }
489
490private:
491
492 //allocate and initialize a stub of the desired kind
493 DispatchHolder *GenerateDispatchStub(PCODE addrOfCode,
494 PCODE addrOfFail,
495 void *pMTExpected,
496 size_t dispatchToken);
497
498#ifdef _TARGET_AMD64_
499 // Used to allocate a long jump dispatch stub. See comment around
500 // m_fShouldAllocateLongJumpDispatchStubs for explaination.
501 DispatchHolder *GenerateDispatchStubLong(PCODE addrOfCode,
502 PCODE addrOfFail,
503 void *pMTExpected,
504 size_t dispatchToken);
505#endif
506
507 ResolveHolder *GenerateResolveStub(PCODE addrOfResolver,
508 PCODE addrOfPatcher,
509 size_t dispatchToken);
510
511 LookupHolder *GenerateLookupStub(PCODE addrOfResolver,
512 size_t dispatchToken);
513
514 VTableCallHolder* GenerateVTableCallStub(DWORD slot);
515
516 template <typename STUB_HOLDER>
517 void AddToCollectibleVSDRangeList(STUB_HOLDER *holder)
518 {
519 if (isCollectible)
520 {
521 parentDomain->GetCollectibleVSDRanges()->AddRange(reinterpret_cast<BYTE *>(holder->stub()),
522 reinterpret_cast<BYTE *>(holder->stub()) + holder->stub()->size(),
523 this);
524 }
525 }
526
527 // The resolve cache is static across all AppDomains
528 ResolveCacheElem *GenerateResolveCacheElem(void *addrOfCode,
529 void *pMTExpected,
530 size_t token);
531
532 ResolveCacheElem *GetResolveCacheElem(void *pMT,
533 size_t token,
534 void *target);
535
536 //Given a dispatch token, an object and a method table, determine the
537 //target address to go to. The return value (BOOL) states whether this address
538 //is cacheable or not.
539 static BOOL Resolver(MethodTable * pMT,
540 DispatchToken token,
541 OBJECTREF * protectedObj,
542 PCODE * ppTarget,
543 BOOL throwOnConflict);
544
545 // This can be used to find a target without needing the ability to throw
546 static BOOL TraceResolver(Object *pObj, DispatchToken token, TraceDestination *trace);
547
548public:
549 // Return the MethodDesc corresponding to this token.
550 static MethodDesc *GetRepresentativeMethodDescFromToken(DispatchToken token, MethodTable *pMT);
551 static MethodDesc *GetInterfaceMethodDescFromToken(DispatchToken token);
552 static MethodTable *GetTypeFromToken(DispatchToken token);
553
554 //This is used to get the token out of a stub
555 static size_t GetTokenFromStub(PCODE stub);
556
557 //This is used to get the token out of a stub and we know the stub manager and stub kind
558 static size_t GetTokenFromStubQuick(VirtualCallStubManager * pMgr, PCODE stub, StubKind kind);
559
560 // General utility functions
561 // Quick lookup in the cache. NOTHROW, GC_NOTRIGGER
562 static PCODE CacheLookup(size_t token, UINT16 tokenHash, MethodTable *pMT);
563
564 // Full exhaustive lookup. THROWS, GC_TRIGGERS
565 static PCODE GetTarget(DispatchToken token, MethodTable *pMT, BOOL throwOnConflict);
566
567private:
568 // Given a dispatch token, return true if the token represents an interface, false if just a slot.
569 static BOOL IsInterfaceToken(DispatchToken token);
570
571 // Given a dispatch token, return true if the token represents a slot on the target.
572 static BOOL IsClassToken(DispatchToken token);
573
574#ifdef CHAIN_LOOKUP
575 static ResolveCacheElem* __fastcall PromoteChainEntry(ResolveCacheElem *pElem);
576#endif
577
578 // Flags used by the non-x86 versions of VSD_ResolveWorker
579
580#define SDF_ResolveBackPatch (0x01)
581#define SDF_ResolvePromoteChain (0x02)
582#define SDF_ResolveFlags (0x03)
583
584 // These method needs to call the instance methods.
585 friend PCODE VSD_ResolveWorker(TransitionBlock * pTransitionBlock,
586 TADDR siteAddrForRegisterIndirect,
587 size_t token
588#ifndef _TARGET_X86_
589 , UINT_PTR flags
590#endif
591 );
592
593#if defined(_TARGET_X86_) && defined(FEATURE_PAL)
594 friend void BackPatchWorkerStaticStub(PCODE returnAddr, TADDR siteAddrForRegisterIndirect);
595#endif
596
597 //These are the entrypoints that the stubs actually end up calling via the
598 // xxxAsmStub methods above
599 static void STDCALL BackPatchWorkerStatic(PCODE returnAddr, TADDR siteAddrForRegisterIndirect);
600
601public:
602 PCODE ResolveWorker(StubCallSite* pCallSite, OBJECTREF *protectedObj, DispatchToken token, StubKind stubKind);
603 void BackPatchWorker(StubCallSite* pCallSite);
604
605 //Change the callsite to point to stub
606 void BackPatchSite(StubCallSite* pCallSite, PCODE stub);
607
608public:
609 /* the following two public functions are to support tracing or stepping thru
610 stubs via the debugger. */
611 virtual BOOL CheckIsStub_Internal(PCODE stubStartAddress);
612 virtual BOOL TraceManager(Thread *thread,
613 TraceDestination *trace,
614 T_CONTEXT *pContext,
615 BYTE **pRetAddr);
616 size_t GetSize()
617 {
618 LIMITED_METHOD_CONTRACT;
619 size_t retval=0;
620 if(indcell_heap)
621 retval+=indcell_heap->GetSize();
622 if(cache_entry_heap)
623 retval+=cache_entry_heap->GetSize();
624 if(lookup_heap)
625 retval+=lookup_heap->GetSize();
626 if(dispatch_heap)
627 retval+=dispatch_heap->GetSize();
628 if(resolve_heap)
629 retval+=resolve_heap->GetSize();
630 return retval;
631 };
632
633private:
634 /* the following two private functions are to support tracing or stepping thru
635 stubs via the debugger. */
636 virtual BOOL DoTraceStub(PCODE stubStartAddress,
637 TraceDestination *trace);
638
639private:
640 // The parent domain of this manager
641 PTR_BaseDomain parentDomain;
642 bool isCollectible;
643
644 BYTE * m_initialReservedMemForHeaps;
645
646 static const UINT32 INDCELLS_PER_BLOCK = 32; // 32 indirection cells per block.
647
648 CrstExplicitInit m_indCellLock;
649
650 // List of free indirection cells. The cells were directly allocated from the loader heap
651 // (code:VirtualCallStubManager::GenerateStubIndirection)
652 BYTE * m_FreeIndCellList;
653
654 // List of recycled indirection cells. The cells were recycled from finalized dynamic methods
655 // (code:LCGMethodResolver::RecycleIndCells).
656 BYTE * m_RecycledIndCellList;
657
658#ifndef DACCESS_COMPILE
659 // This methods returns the a free cell from m_FreeIndCellList. It returns NULL if the list is empty.
660 BYTE * GetOneFreeIndCell()
661 {
662 WRAPPER_NO_CONTRACT;
663
664 return GetOneIndCell(&m_FreeIndCellList);
665 }
666
667 // This methods returns the a recycled cell from m_RecycledIndCellList. It returns NULL if the list is empty.
668 BYTE * GetOneRecycledIndCell()
669 {
670 WRAPPER_NO_CONTRACT;
671
672 return GetOneIndCell(&m_RecycledIndCellList);
673 }
674
675 // This methods returns the a cell from ppList. It returns NULL if the list is empty.
676 BYTE * GetOneIndCell(BYTE ** ppList)
677 {
678 CONTRACT (BYTE*) {
679 NOTHROW;
680 GC_NOTRIGGER;
681 MODE_ANY;
682 PRECONDITION(CheckPointer(ppList));
683 PRECONDITION(m_indCellLock.OwnedByCurrentThread());
684 } CONTRACT_END;
685
686 BYTE * temp = *ppList;
687
688 if (temp)
689 {
690 BYTE * pNext = *((BYTE **)temp);
691 *ppList = pNext;
692 RETURN temp;
693 }
694
695 RETURN NULL;
696 }
697
698 // insert a linked list of indirection cells at the beginning of m_FreeIndCellList
699 void InsertIntoFreeIndCellList(BYTE * head, BYTE * tail)
700 {
701 WRAPPER_NO_CONTRACT;
702
703 InsertIntoIndCellList(&m_FreeIndCellList, head, tail);
704 }
705
706 // insert a linked list of indirection cells at the beginning of ppList
707 void InsertIntoIndCellList(BYTE ** ppList, BYTE * head, BYTE * tail)
708 {
709 CONTRACTL {
710 NOTHROW;
711 GC_NOTRIGGER;
712 PRECONDITION(CheckPointer(ppList));
713 PRECONDITION(CheckPointer(head));
714 PRECONDITION(CheckPointer(tail));
715 PRECONDITION(m_indCellLock.OwnedByCurrentThread());
716 } CONTRACTL_END;
717
718 BYTE * temphead = *ppList;
719 *((BYTE**)tail) = temphead;
720 *ppList = head;
721 }
722#endif // !DACCESS_COMPILE
723
724 PTR_LoaderHeap indcell_heap; // indirection cells go here
725 PTR_LoaderHeap cache_entry_heap; // resolve cache elem entries go here
726 PTR_LoaderHeap lookup_heap; // lookup stubs go here
727 PTR_LoaderHeap dispatch_heap; // dispatch stubs go here
728 PTR_LoaderHeap resolve_heap; // resolve stubs go here
729 PTR_LoaderHeap vtable_heap; // vtable-based jump stubs go here
730
731#ifdef _TARGET_AMD64_
732 // When we layout the stub heaps, we put them close together in a sequential order
733 // so that we maximize performance with respect to branch predictions. On AMD64,
734 // dispatch stubs use a rel32 jump on failure to the resolve stub. This works for
735 // a while because of the ordering, but as soon as we have to start allocating more
736 // memory for either the dispatch or resolve heaps we have a chance that we'll be
737 // further away than a rel32 jump can reach, because we're in a 64-bit address
738 // space. As such, this flag will indicate when we allocate the first dispatch stub
739 // that cannot reach a resolve stub, and when this happens we'll switch over to
740 // allocating the larger version of the dispatch stub which contains an abs64 jump.
741 //@TODO: This is a bit of a workaround, but the limitations of LoaderHeap require that we
742 //@TODO: take this approach. Hopefully in Orcas we'll have a chance to rewrite LoaderHeap.
743 BOOL m_fShouldAllocateLongJumpDispatchStubs; // Defaults to FALSE.
744#endif
745
746 BucketTable * lookups; // hash table of lookups keyed by tokens
747 BucketTable * cache_entries; // hash table of dispatch token/target structs for dispatch cache
748 BucketTable * dispatchers; // hash table of dispatching stubs keyed by tokens/actualtype
749 BucketTable * resolvers; // hash table of resolvers keyed by tokens/resolverstub
750 BucketTable * vtableCallers; // hash table of vtable call stubs keyed by slot values
751
752 // This structure is used to keep track of the fail counters.
753 // We only need one fail counter per ResolveStub,
754 // and most programs use less than 250 ResolveStubs
755 // We allocate these on the main heap using "new counter block"
756 struct counter_block
757 {
758 static const UINT32 MAX_COUNTER_ENTRIES = 256-2; // 254 counters should be enough for most cases.
759
760 counter_block * next; // the next block
761 UINT32 used; // the index of the next free entry
762 INT32 block[MAX_COUNTER_ENTRIES]; // the counters
763 };
764
765 counter_block *m_counters; // linked list of counter blocks of failure counters
766 counter_block *m_cur_counter_block; // current block for updating counts
767 counter_block *m_cur_counter_block_for_reclaim; // current block for updating
768 UINT32 m_cur_counter_block_for_reclaim_index; // index into the current block for updating
769
770 // Used to keep track of all the VCSManager objects in the system.
771 PTR_VirtualCallStubManager m_pNext; // Linked list pointer
772
773public:
774 // Given a stub address, find the VCSManager that owns it.
775 static VirtualCallStubManager *FindStubManager(PCODE addr,
776 StubKind* wbStubKind = NULL,
777 BOOL usePredictStubKind = TRUE);
778
779#ifndef DACCESS_COMPILE
780 // insert a linked list of indirection cells at the beginning of m_RecycledIndCellList
781 void InsertIntoRecycledIndCellList_Locked(BYTE * head, BYTE * tail)
782 {
783 CONTRACTL {
784 NOTHROW;
785 GC_TRIGGERS;
786 MODE_ANY;
787 } CONTRACTL_END;
788
789 CrstHolder lh(&m_indCellLock);
790
791 InsertIntoIndCellList(&m_RecycledIndCellList, head, tail);
792 }
793#endif // !DACCESS_COMPILE
794
795 // These are the counters for keeping statistics
796 struct
797 {
798 UINT32 site_counter; //# of call sites
799 UINT32 stub_lookup_counter; //# of lookup stubs
800 UINT32 stub_poly_counter; //# of resolve stubs
801 UINT32 stub_mono_counter; //# of dispatch stubs
802 UINT32 stub_vtable_counter; //# of vtable call stubs
803 UINT32 site_write; //# of call site backpatch writes
804 UINT32 site_write_poly; //# of call site backpatch writes to point to resolve stubs
805 UINT32 site_write_mono; //# of call site backpatch writes to point to dispatch stubs
806 UINT32 worker_call; //# of calls into ResolveWorker
807 UINT32 worker_call_no_patch; //# of times call_worker resulted in no patch
808 UINT32 worker_collide_to_mono; //# of times we converted a poly stub to a mono stub instead of writing the cache entry
809 UINT32 stub_space; //# of bytes of stubs
810 UINT32 cache_entry_counter; //# of cache structs
811 UINT32 cache_entry_space; //# of bytes used by cache lookup structs
812 } stats;
813
814 void LogStats();
815
816#ifdef DACCESS_COMPILE
817protected:
818 virtual void DoEnumMemoryRegions(CLRDataEnumMemoryFlags flags);
819 virtual LPCWSTR GetStubManagerName(PCODE addr)
820 {
821 WRAPPER_NO_CONTRACT;
822 CONSISTENCY_CHECK(isStub(addr));
823
824 if (isLookupStub(addr))
825 {
826 return W("VSD_LookupStub");
827 }
828 else if (isDispatchingStub(addr))
829 {
830 return W("VSD_DispatchStub");
831 }
832 else
833 {
834 CONSISTENCY_CHECK(isResolvingStub(addr));
835 return W("VSD_ResolveStub");
836 }
837 }
838#endif
839};
840
841/********************************************************************************************************
842********************************************************************************************************/
843typedef VPTR(class VirtualCallStubManagerManager) PTR_VirtualCallStubManagerManager;
844
845class VirtualCallStubManagerIterator;
846class VirtualCallStubManagerManager : public StubManager
847{
848 VPTR_VTABLE_CLASS(VirtualCallStubManagerManager, StubManager)
849
850 friend class StubManager;
851 friend class VirtualCallStubManager;
852 friend class VirtualCallStubManagerIterator;
853 friend class StubManagerIterator;
854
855 public:
856 virtual BOOL TraceManager(Thread *thread, TraceDestination *trace,
857 T_CONTEXT *pContext, BYTE **pRetAddr);
858
859 virtual BOOL CheckIsStub_Internal(PCODE stubStartAddress);
860
861 virtual BOOL DoTraceStub(PCODE stubStartAddress, TraceDestination *trace);
862
863 static MethodDesc *Entry2MethodDesc(PCODE stubStartAddress, MethodTable *pMT);
864
865#ifdef DACCESS_COMPILE
866 virtual void DoEnumMemoryRegions(CLRDataEnumMemoryFlags flags);
867 virtual LPCWSTR GetStubManagerName(PCODE addr)
868 { WRAPPER_NO_CONTRACT; return FindVirtualCallStubManager(addr)->GetStubManagerName(addr); }
869#endif
870
871 private:
872 // Used to keep track of all the VCSManager objects in the system.
873 PTR_VirtualCallStubManager m_pManagers; // Head of the linked list
874
875#ifndef DACCESS_COMPILE
876 // Ctor. This is only used by StaticInit.
877 VirtualCallStubManagerManager();
878#endif
879
880 // A cache element to quickly check the last matched manager.
881 Volatile<VirtualCallStubManager*> m_pCacheElem;
882
883 // RW lock for reading entries and removing them.
884 SimpleRWLock m_RWLock;
885
886 // This will look through all the managers in an intelligent fashion to
887 // find the manager that owns the address.
888 VirtualCallStubManager *FindVirtualCallStubManager(PCODE stubAddress);
889
890 protected:
891 // Add a VCSManager to the linked list.
892 void AddStubManager(VirtualCallStubManager *pMgr);
893
894 // Remove a VCSManager from the linked list.
895 void RemoveStubManager(VirtualCallStubManager *pMgr);
896
897 VirtualCallStubManager *FirstManager()
898 { WRAPPER_NO_CONTRACT; return m_pManagers; }
899
900#ifndef DACCESS_COMPILE
901 static void InitStatic();
902#endif
903
904 public:
905 SPTR_DECL(VirtualCallStubManagerManager, g_pManager);
906
907 static VirtualCallStubManagerManager *GlobalManager()
908 { LIMITED_METHOD_DAC_CONTRACT; CONSISTENCY_CHECK(CheckPointer(g_pManager)); return g_pManager; }
909
910 VirtualCallStubManagerIterator IterateVirtualCallStubManagers();
911
912#ifdef _DEBUG
913 // Debug helper to help identify stub-managers.
914 virtual const char * DbgGetName() { LIMITED_METHOD_CONTRACT; return "VirtualCallStubManagerManager"; }
915#endif
916};
917
918/********************************************************************************************************
919********************************************************************************************************/
920class VirtualCallStubManagerIterator
921{
922 friend class VirtualCallStubManagerManager;
923
924 public:
925 BOOL Next();
926 VirtualCallStubManager *Current();
927
928 // Copy ctor
929 inline VirtualCallStubManagerIterator(const VirtualCallStubManagerIterator &it);
930
931 protected:
932 inline VirtualCallStubManagerIterator(VirtualCallStubManagerManager *pMgr);
933
934 BOOL m_fIsStart;
935 VirtualCallStubManager *m_pCurMgr;
936};
937
938/////////////////////////////////////////////////////////////////////////////////////////////
939// Ctor
940inline VirtualCallStubManagerIterator::VirtualCallStubManagerIterator(VirtualCallStubManagerManager *pMgr)
941 : m_fIsStart(TRUE), m_pCurMgr(pMgr->m_pManagers)
942{
943 LIMITED_METHOD_DAC_CONTRACT;
944 CONSISTENCY_CHECK(CheckPointer(pMgr));
945}
946
947/////////////////////////////////////////////////////////////////////////////////////////////
948// Copy ctor
949inline VirtualCallStubManagerIterator::VirtualCallStubManagerIterator(const VirtualCallStubManagerIterator &it)
950 : m_fIsStart(it.m_fIsStart), m_pCurMgr(it.m_pCurMgr)
951{
952 LIMITED_METHOD_DAC_CONTRACT;
953}
954
955/********************************************************************************************************
956#StubDispatchNotes
957
958A note on approach. The cache and hash tables used by the stub and lookup mechanism
959are designed with an eye to minimizing interlocking and/or syncing and/or locking operations.
960They are intended to run in a highly concurrent environment. Since there is no magic,
961some tradeoffs and and some implementation constraints are required. The basic notion
962is that if all reads and writes are atomic and if all functions and operations operate
963correctly in the face of commutative reorderings of the visibility of all reads and writes
964across threads, then we don't have to interlock, sync, or serialize. Our approximation of
965this is:
966
9671. All reads and all writes to tables must be atomic. This effectively limits the actual entry
968size in a table to be a pointer or a pointer sized thing.
969
9702. All functions, like comparisons for equality or computation of hash values must function
971correctly in the face of concurrent updating of the underlying table. This is accomplished
972by making the underlying structures/entries effectively immutable, if concurrency is in anyway possible.
973By effectively immutatable, we mean that the stub or token structure is either immutable or that
974if it is ever written, all possibley concurrent writes are attempting to write the same value (atomically)
975or that the competing (atomic) values do not affect correctness, and that the function operates correctly whether
976or not any of the writes have taken place (is visible yet). The constraint we maintain is that all competeing
977updates (and their visibility or lack thereof) do not alter the correctness of the program.
978
9793. All tables are inexact. The counts they hold (e.g. number of contained entries) may be inaccurrate,
980but that inaccurracy cannot affect their correctness. Table modifications, such as insertion of
981an new entry may not succeed, but such failures cannot affect correctness. This implies that just
982because a stub/entry is not present in a table, e.g. has been removed, that does not mean that
983it is not in use. It also implies that internal table structures, such as discarded hash table buckets,
984cannot be freely recycled since another concurrent thread may still be walking thru it.
985
9864. Occassionaly it is necessary to pick up the pieces that have been dropped on the floor
987so to speak, e.g. actually recycle hash buckets that aren't in use. Since we have a natural
988sync point already in the GC, we use that to provide cleanup points. We need to make sure that code that
989is walking our structures is not a GC safe point. Hence if the GC calls back into us inside the GC
990sync point, we know that nobody is inside our stuctures and we can safely rearrange and recycle things.
991********************************************************************************************************/
992
993//initial and increment value for fail stub counters
994#ifdef STUB_LOGGING
995extern UINT32 STUB_MISS_COUNT_VALUE;
996extern UINT32 STUB_COLLIDE_WRITE_PCT;
997extern UINT32 STUB_COLLIDE_MONO_PCT;
998#else // !STUB_LOGGING
999#define STUB_MISS_COUNT_VALUE 100
1000#define STUB_COLLIDE_WRITE_PCT 100
1001#define STUB_COLLIDE_MONO_PCT 0
1002#endif // !STUB_LOGGING
1003
1004//size and mask of the cache used by resolve stubs
1005// CALL_STUB_CACHE_SIZE must be equal to 2^CALL_STUB_CACHE_NUM_BITS
1006#define CALL_STUB_CACHE_NUM_BITS 12 //10
1007#define CALL_STUB_CACHE_SIZE 4096 //1024
1008#define CALL_STUB_CACHE_MASK (CALL_STUB_CACHE_SIZE-1)
1009#define CALL_STUB_CACHE_PROBES 5
1010//min sizes for BucketTable and buckets and the growth and hashing constants
1011#define CALL_STUB_MIN_BUCKETS 32
1012#define CALL_STUB_MIN_ENTRIES 4
1013//this is so that the very first growth will jump from 4 to 32 entries, then double from there.
1014#define CALL_STUB_SECONDARY_ENTRIES 8
1015#define CALL_STUB_GROWTH_FACTOR 2
1016#define CALL_STUB_LOAD_FACTOR 90
1017#define CALL_STUB_HASH_CONST1 1327
1018#define CALL_STUB_HASH_CONST2 43627
1019#define LARGE_PRIME 7199369
1020//internal layout of buckets=size-1,count,entries....
1021#define CALL_STUB_MASK_INDEX 0
1022#define CALL_STUB_COUNT_INDEX 1
1023#define CALL_STUB_DEAD_LINK 2
1024#define CALL_STUB_FIRST_INDEX 3
1025//marker entries in cache and hash tables
1026#define CALL_STUB_EMPTY_ENTRY 0
1027// number of successes for a chained element before it gets moved to the front
1028#define CALL_STUB_CACHE_INITIAL_SUCCESS_COUNT (0x100)
1029
1030/*******************************************************************************************************
1031Entry is an abstract class. We will make specific subclasses for each kind of
1032entry. Entries hold references to stubs or tokens. The principle thing they provide
1033is a virtual Equals function that is used by the caching and hashing tables within which
1034the stubs and tokens are stored. Entries are typically stack allocated by the routines
1035that call into the hash and caching functions, and the functions stuff stubs into the entry
1036to do the comparisons. Essentially specific entry subclasses supply a vtable to a stub
1037as and when needed. This means we don't have to have vtables attached to stubs.
1038
1039Summarizing so far, there is a struct for each kind of stub or token of the form XXXXStub.
1040They provide that actual storage layouts.
1041There is a stuct in which each stub which has code is containted of the form XXXXHolder.
1042They provide alignment and anciliary storage for the stub code.
1043There is a subclass of Entry for each kind of stub or token, of the form XXXXEntry.
1044They provide the specific implementations of the virtual functions declared in Entry. */
1045class Entry
1046{
1047public:
1048 //access and compare the keys of the entry
1049 virtual BOOL Equals(size_t keyA, size_t keyB)=0;
1050 virtual size_t KeyA()=0;
1051 virtual size_t KeyB()=0;
1052
1053 //contents is the struct or token that the entry exposes
1054 virtual void SetContents(size_t contents)=0;
1055};
1056
1057/* define the platform specific Stubs and stub holders */
1058
1059#include <virtualcallstubcpu.hpp>
1060
1061#if USES_LOOKUP_STUBS
1062/**********************************************************************************************
1063LookupEntry wraps LookupStubs and provide the concrete implementation of the abstract class Entry.
1064Virtual and interface call sites when they are first jitted point to LookupStubs. The hash table
1065that contains look up stubs is keyed by token, hence the Equals function uses the embedded token in
1066the stub for comparison purposes. Since we are willing to allow duplicates in the hash table (as
1067long as they are relatively rare) we do use direct comparison of the tokens rather than extracting
1068the fields from within the tokens, for perf reasons. */
1069class LookupEntry : public Entry
1070{
1071public:
1072 //Creates an entry that wraps lookup stub s
1073 LookupEntry(size_t s)
1074 {
1075 LIMITED_METHOD_CONTRACT;
1076 _ASSERTE(VirtualCallStubManager::isLookupStubStatic((PCODE)s));
1077 stub = (LookupStub*) s;
1078 }
1079
1080 //default contructor to allow stack and inline allocation of lookup entries
1081 LookupEntry() {LIMITED_METHOD_CONTRACT; stub = NULL;}
1082
1083 //implementations of abstract class Entry
1084 BOOL Equals(size_t keyA, size_t keyB)
1085 { WRAPPER_NO_CONTRACT; return stub && (keyA == KeyA()) && (keyB == KeyB()); }
1086
1087 size_t KeyA() { WRAPPER_NO_CONTRACT; return Token(); }
1088 size_t KeyB() { WRAPPER_NO_CONTRACT; return (size_t)0; }
1089
1090 void SetContents(size_t contents)
1091 {
1092 LIMITED_METHOD_CONTRACT;
1093 _ASSERTE(VirtualCallStubManager::isLookupStubStatic((PCODE)contents));
1094 stub = LookupHolder::FromLookupEntry((PCODE)contents)->stub();
1095 }
1096
1097 //extract the token of the underlying lookup stub
1098
1099 inline size_t Token() { LIMITED_METHOD_CONTRACT; return stub ? stub->token() : 0; }
1100
1101private:
1102 LookupStub* stub; //the stub the entry wrapping
1103};
1104#endif // USES_LOOKUP_STUBS
1105
1106class VTableCallEntry : public Entry
1107{
1108public:
1109 //Creates an entry that wraps vtable call stub
1110 VTableCallEntry(size_t s)
1111 {
1112 LIMITED_METHOD_CONTRACT;
1113 _ASSERTE(VirtualCallStubManager::isVtableCallStubStatic((PCODE)s));
1114 stub = (VTableCallStub*)s;
1115 }
1116
1117 //default contructor to allow stack and inline allocation of vtable call entries
1118 VTableCallEntry() { LIMITED_METHOD_CONTRACT; stub = NULL; }
1119
1120 //implementations of abstract class Entry
1121 BOOL Equals(size_t keyA, size_t keyB)
1122 {
1123 WRAPPER_NO_CONTRACT; return stub && (keyA == KeyA()) && (keyB == KeyB());
1124 }
1125
1126 size_t KeyA() { WRAPPER_NO_CONTRACT; return Token(); }
1127 size_t KeyB() { WRAPPER_NO_CONTRACT; return (size_t)0; }
1128
1129 void SetContents(size_t contents)
1130 {
1131 LIMITED_METHOD_CONTRACT;
1132 _ASSERTE(VirtualCallStubManager::isVtableCallStubStatic((PCODE)contents));
1133 stub = VTableCallHolder::FromVTableCallEntry((PCODE)contents)->stub();
1134 }
1135
1136 //extract the token of the underlying lookup stub
1137
1138 inline size_t Token() { LIMITED_METHOD_CONTRACT; return stub ? stub->token() : 0; }
1139
1140private:
1141 VTableCallStub* stub; //the stub the entry wrapping
1142};
1143
1144/**********************************************************************************************
1145ResolveCacheEntry wraps a ResolveCacheElem and provides lookup functionality for entries that
1146were created that may be added to the ResolveCache
1147*/
1148class ResolveCacheEntry : public Entry
1149{
1150public:
1151 ResolveCacheEntry(size_t elem)
1152 {
1153 LIMITED_METHOD_CONTRACT;
1154 _ASSERTE(elem != 0);
1155 pElem = (ResolveCacheElem*) elem;
1156 }
1157
1158 //default contructor to allow stack and inline allocation of lookup entries
1159 ResolveCacheEntry() { LIMITED_METHOD_CONTRACT; pElem = NULL; }
1160
1161 //access and compare the keys of the entry
1162 virtual BOOL Equals(size_t keyA, size_t keyB)
1163 { WRAPPER_NO_CONTRACT; return pElem && (keyA == KeyA()) && (keyB == KeyB()); }
1164 virtual size_t KeyA()
1165 { LIMITED_METHOD_CONTRACT; return pElem != NULL ? pElem->token : 0; }
1166 virtual size_t KeyB()
1167 { LIMITED_METHOD_CONTRACT; return pElem != NULL ? (size_t) pElem->pMT : 0; }
1168
1169 //contents is the struct or token that the entry exposes
1170 virtual void SetContents(size_t contents)
1171 {
1172 LIMITED_METHOD_CONTRACT;
1173 pElem = (ResolveCacheElem*) contents;
1174 }
1175
1176 inline const BYTE *Target()
1177 {
1178 LIMITED_METHOD_CONTRACT;
1179 return pElem != NULL ? (const BYTE *)pElem->target : NULL;
1180 }
1181
1182private:
1183 ResolveCacheElem *pElem;
1184};
1185
1186/**********************************************************************************************
1187ResolveEntry wraps ResolveStubs and provide the concrete implementation of the abstract class Entry.
1188Polymorphic call sites and monomorphic calls that fail end up in a ResolveStub. Resolve stubs
1189are stored in hash tables keyed by token, hence the Equals function uses the embedded token in
1190the stub for comparison purposes. Since we are willing to allow duplicates in the hash table (as
1191long as they are relatively rare) we do use direct comparison of the tokens rather than extracting
1192the fields from within the tokens, for perf reasons. */
1193class ResolveEntry : public Entry
1194{
1195public:
1196 //Creates an entry that wraps resolve stub s
1197 ResolveEntry (size_t s)
1198 {
1199 LIMITED_METHOD_CONTRACT;
1200 _ASSERTE(VirtualCallStubManager::isResolvingStubStatic((PCODE)s));
1201 stub = (ResolveStub*) s;
1202 }
1203 //default contructor to allow stack and inline allocation of resovler entries
1204 ResolveEntry() { LIMITED_METHOD_CONTRACT; stub = CALL_STUB_EMPTY_ENTRY; }
1205
1206 //implementations of abstract class Entry
1207 inline BOOL Equals(size_t keyA, size_t keyB)
1208 { WRAPPER_NO_CONTRACT; return stub && (keyA == KeyA()) && (keyB == KeyB()); }
1209 inline size_t KeyA() { WRAPPER_NO_CONTRACT; return Token(); }
1210 inline size_t KeyB() { WRAPPER_NO_CONTRACT; return (size_t)0; }
1211
1212 void SetContents(size_t contents)
1213 {
1214 LIMITED_METHOD_CONTRACT;
1215 _ASSERTE(VirtualCallStubManager::isResolvingStubStatic((PCODE)contents));
1216 stub = ResolveHolder::FromResolveEntry((PCODE)contents)->stub();
1217 }
1218 //extract the token of the underlying resolve stub
1219 inline size_t Token() { WRAPPER_NO_CONTRACT; return stub ? (size_t)(stub->token()) : 0; }
1220private:
1221 ResolveStub* stub; //the stub the entry is wrapping
1222};
1223
1224/**********************************************************************************************
1225DispatchEntry wraps DispatchStubs and provide the concrete implementation of the abstract class Entry.
1226Monomorphic and mostly monomorphic call sites eventually point to DispatchStubs. Dispatch stubs
1227are placed in hash and cache tables keyed by the expected Method Table and token they are built for.
1228Since we are willing to allow duplicates in the hash table (as long as they are relatively rare)
1229we do use direct comparison of the tokens rather than extracting the fields from within the tokens,
1230for perf reasons.*/
1231class DispatchEntry : public Entry
1232{
1233public:
1234 //Creates an entry that wraps dispatch stub s
1235 DispatchEntry (size_t s)
1236 {
1237 LIMITED_METHOD_CONTRACT;
1238 _ASSERTE(VirtualCallStubManager::isDispatchingStubStatic((PCODE)s));
1239 stub = (DispatchStub*) s;
1240 }
1241 //default contructor to allow stack and inline allocation of resovler entries
1242 DispatchEntry() { LIMITED_METHOD_CONTRACT; stub = CALL_STUB_EMPTY_ENTRY; }
1243
1244 //implementations of abstract class Entry
1245 inline BOOL Equals(size_t keyA, size_t keyB)
1246 { WRAPPER_NO_CONTRACT; return stub && (keyA == KeyA()) && (keyB == KeyB()); }
1247 inline size_t KeyA() { WRAPPER_NO_CONTRACT; return Token(); }
1248 inline size_t KeyB() { WRAPPER_NO_CONTRACT; return ExpectedMT();}
1249
1250 void SetContents(size_t contents)
1251 {
1252 LIMITED_METHOD_CONTRACT;
1253 _ASSERTE(VirtualCallStubManager::isDispatchingStubStatic((PCODE)contents));
1254 stub = DispatchHolder::FromDispatchEntry((PCODE)contents)->stub();
1255 }
1256
1257 //extract the fields of the underlying dispatch stub
1258 inline size_t ExpectedMT()
1259 { WRAPPER_NO_CONTRACT; return stub ? (size_t)(stub->expectedMT()) : 0; }
1260
1261 size_t Token()
1262 {
1263 WRAPPER_NO_CONTRACT;
1264 if (stub)
1265 {
1266 ResolveHolder * resolveHolder = ResolveHolder::FromFailEntry(stub->failTarget());
1267 size_t token = resolveHolder->stub()->token();
1268 _ASSERTE(token == VirtualCallStubManager::GetTokenFromStub((PCODE)stub));
1269 return token;
1270 }
1271 else
1272 {
1273 return 0;
1274 }
1275 }
1276
1277 inline PCODE Target()
1278 { WRAPPER_NO_CONTRACT; return stub ? stub->implTarget() : 0; }
1279
1280private:
1281 DispatchStub* stub;
1282};
1283
1284/*************************************************************************************************
1285DispatchCache is the cache table that the resolve stubs use for inline polymorphic resolution
1286of a call. The cache entry is logically a triplet of (method table, token, impl address) where method table
1287is the type of the calling frame's <this>, token identifies the method being invoked,
1288i.e. is a (type id,slot #) pair, and impl address is the address of the method implementation.
1289*/
1290class DispatchCache
1291{
1292public:
1293 static const UINT16 INVALID_HASH = (UINT16)(-1);
1294
1295 DispatchCache();
1296
1297 //read and write the cache keyed by (method table,token) pair.
1298 inline ResolveCacheElem* Lookup(size_t token, void* mt)
1299 { WRAPPER_NO_CONTRACT; return Lookup(token, INVALID_HASH, mt);}
1300
1301 ResolveCacheElem* Lookup(size_t token, UINT16 tokenHash, void* mt);
1302
1303 enum InsertKind {IK_NONE, IK_DISPATCH, IK_RESOLVE, IK_SHARED, IK_EXTERNAL};
1304
1305 BOOL Insert(ResolveCacheElem* elem, InsertKind insertKind);
1306#ifdef CHAIN_LOOKUP
1307 void PromoteChainEntry(ResolveCacheElem* elem);
1308#endif
1309
1310 // This is the heavyweight hashing algorithm. Use sparingly.
1311 static UINT16 HashToken(size_t token);
1312
1313 inline void GetLoadFactor(size_t *total, size_t *used)
1314 {
1315 LIMITED_METHOD_CONTRACT;
1316
1317 *total = CALL_STUB_CACHE_SIZE;
1318 size_t count = 0;
1319 for (size_t i = 0; i < CALL_STUB_CACHE_SIZE; i++)
1320 if (cache[i] != empty)
1321 count++;
1322 *used = count;
1323 }
1324
1325 inline void *GetCacheBaseAddr()
1326 { LIMITED_METHOD_CONTRACT; return &cache[0]; }
1327 inline size_t GetCacheCount()
1328 { LIMITED_METHOD_CONTRACT; return CALL_STUB_CACHE_SIZE; }
1329 inline ResolveCacheElem *GetCacheEntry(size_t idx)
1330 { LIMITED_METHOD_CONTRACT; return VolatileLoad(&cache[idx]); }
1331 inline BOOL IsCacheEntryEmpty(size_t idx)
1332 { LIMITED_METHOD_CONTRACT; return cache[idx] == empty; }
1333
1334 inline void SetCacheEntry(size_t idx, ResolveCacheElem *elem)
1335 {
1336 LIMITED_METHOD_CONTRACT;
1337#ifdef STUB_LOGGING
1338 cacheData[idx].numWrites++;
1339#endif
1340#ifdef CHAIN_LOOKUP
1341 CONSISTENCY_CHECK(m_writeLock.OwnedByCurrentThread());
1342#endif
1343 cache[idx] = elem;
1344 }
1345
1346 inline void ClearCacheEntry(size_t idx)
1347 {
1348 LIMITED_METHOD_CONTRACT;
1349#ifdef STUB_LOGGING
1350 cacheData[idx].numClears++;
1351#endif
1352 cache[idx] = empty;
1353 }
1354
1355 struct
1356 {
1357 UINT32 insert_cache_external; //# of times Insert was called for IK_EXTERNAL
1358 UINT32 insert_cache_shared; //# of times Insert was called for IK_SHARED
1359 UINT32 insert_cache_dispatch; //# of times Insert was called for IK_DISPATCH
1360 UINT32 insert_cache_resolve; //# of times Insert was called for IK_RESOLVE
1361 UINT32 insert_cache_hit; //# of times Insert found an empty cache entry
1362 UINT32 insert_cache_miss; //# of times Insert already had a matching cache entry
1363 UINT32 insert_cache_collide; //# of times Insert found a used cache entry
1364 UINT32 insert_cache_write; //# of times Insert wrote a cache entry
1365 } stats;
1366
1367 void LogStats();
1368
1369 // Unlocked iterator of entries. Use only when read/write access to the cache
1370 // is safe. This would typically be at GC sync points, currently needed during
1371 // appdomain unloading.
1372 class Iterator
1373 {
1374 public:
1375 Iterator(DispatchCache *pCache);
1376 inline BOOL IsValid()
1377 { WRAPPER_NO_CONTRACT; return (m_curBucket < (INT32)m_pCache->GetCacheCount()); }
1378 void Next();
1379 // Unlink the current entry.
1380 // **NOTE** Using this method implicitly performs a call to Next to move
1381 // past the unlinked entry. Thus, one could accidentally skip
1382 // entries unless you take this into consideration.
1383 ResolveCacheElem *UnlinkEntry();
1384 inline ResolveCacheElem *Entry()
1385 { LIMITED_METHOD_CONTRACT; CONSISTENCY_CHECK(IsValid()); return *m_ppCurElem; }
1386
1387 private:
1388 void NextValidBucket();
1389 inline void NextBucket()
1390 { LIMITED_METHOD_CONTRACT; m_curBucket++; m_ppCurElem = &m_pCache->cache[m_curBucket]; }
1391
1392 DispatchCache *m_pCache;
1393 INT32 m_curBucket;
1394 ResolveCacheElem **m_ppCurElem;
1395 };
1396
1397private:
1398#ifdef CHAIN_LOOKUP
1399 Crst m_writeLock;
1400#endif
1401
1402 //the following hash computation is also inlined in the resolve stub in asm (SO NO TOUCHIE)
1403 inline static UINT16 HashMT(UINT16 tokenHash, void* mt)
1404 {
1405 LIMITED_METHOD_CONTRACT;
1406
1407 UINT16 hash;
1408
1409 size_t mtHash = (size_t) mt;
1410 mtHash = (((mtHash >> CALL_STUB_CACHE_NUM_BITS) + mtHash) >> LOG2_PTRSIZE) & CALL_STUB_CACHE_MASK;
1411 hash = (UINT16) mtHash;
1412
1413 hash ^= (tokenHash & CALL_STUB_CACHE_MASK);
1414
1415 return hash;
1416 }
1417
1418 ResolveCacheElem* cache[CALL_STUB_CACHE_SIZE]; //must be first
1419 ResolveCacheElem* empty; //empty entry, initialized to fail all comparisons
1420#ifdef STUB_LOGGING
1421public:
1422 struct CacheEntryData {
1423 UINT32 numWrites;
1424 UINT16 numClears;
1425 };
1426 CacheEntryData cacheData[CALL_STUB_CACHE_SIZE];
1427#endif // STUB_LOGGING
1428};
1429
1430/**************************************************************************************************
1431The hash tables are accessed via instances of the Prober. Prober is a probe into a bucket
1432of the hash table, and therefore has an index which is the current probe position.
1433It includes a count of the number of probes done in that bucket so far and a stride
1434to step thru the bucket with. To do comparisons, it has a reference to an entry with which
1435it can do comparisons (Equals(...)) of the entries (stubs) inside the hash table. It also has
1436the key pair (keyA, keyB) that it is looking for.
1437
1438Typically, an entry of the appropriate type is created on the stack and then the prober is created passing
1439in a reference to the entry. The prober is used for a complete operation, such as look for and find an
1440entry (stub), creating and inserting it as necessary.
1441
1442The initial index and the stride are orthogonal hashes of the key pair, i.e. we are doing a varient of
1443double hashing. When we initialize the prober (see FormHash below) we set the initial probe based on
1444one hash. The stride (used as a modulo addition of the probe position) is based on a different hash and
1445is such that it will vist every location in the bucket before repeating. Hence it is imperative that
1446the bucket size and the stride be relative prime wrt each other. We have chosen to make bucket sizes
1447a power of 2, so we force stride to be odd.
1448
1449Note -- it must be assumed that multiple probers are walking the same tables and buckets at the same time.
1450Additionally, the counts may not be accurate, and there may be duplicates in the tables. Since the tables
1451do not allow concurrrent deletion, some of the concurrency issues are ameliorated.
1452*/
1453class Prober
1454{
1455 friend class FastTable;
1456 friend class BucketTable;
1457public:
1458 Prober(Entry* e) {LIMITED_METHOD_CONTRACT; comparer = e;}
1459 //find the requested entry, if not there return CALL_STUB_EMPTY_ENTRY
1460 size_t Find();
1461 //add the entry into the bucket, if it is not already in the bucket.
1462 //return the entry actually in the bucket (existing or added)
1463 size_t Add(size_t entry);
1464private:
1465 //return the bucket (FastTable*) that the prober is currently walking
1466 inline size_t* items() {LIMITED_METHOD_CONTRACT; return &base[-CALL_STUB_FIRST_INDEX];}
1467 //are there more probes possible, or have we probed everything in the bucket
1468 inline BOOL NoMore() {LIMITED_METHOD_CONTRACT; return probes>mask;} //both probes and mask are (-1)
1469 //advance the probe to a new place in the bucket
1470 inline BOOL Next()
1471 {
1472 WRAPPER_NO_CONTRACT;
1473 index = (index + stride) & mask;
1474 probes++;
1475 return !NoMore();
1476 }
1477 inline size_t Read()
1478 {
1479 LIMITED_METHOD_CONTRACT;
1480 _ASSERTE(base);
1481 return VolatileLoad(&base[index]);
1482 }
1483 //initialize a prober across a bucket (table) for the specified keys.
1484 void InitProber(size_t key1, size_t key2, size_t* table);
1485 //set up the initial index and stride and probe count
1486 inline void FormHash()
1487 {
1488 LIMITED_METHOD_CONTRACT;
1489
1490 probes = 0;
1491 //these two hash functions have not been formally measured for effectiveness
1492 //but they are at least orthogonal
1493
1494 size_t a = ((keyA>>16) + keyA);
1495 size_t b = ((keyB>>16) ^ keyB);
1496 index = (((a*CALL_STUB_HASH_CONST1)>>4)+((b*CALL_STUB_HASH_CONST2)>>4)+CALL_STUB_HASH_CONST1) & mask;
1497 stride = ((a+(b*CALL_STUB_HASH_CONST1)+CALL_STUB_HASH_CONST2) | 1) & mask;
1498 }
1499 //atomically grab an empty slot so we can insert a new entry into the bucket
1500 BOOL GrabEntry(size_t entryValue);
1501 size_t keyA; //key pair we are looking for
1502 size_t keyB;
1503 size_t* base; //we have our own pointer to the bucket, so races don't matter.
1504 // We won't care if we do the lookup in an
1505 // outdated bucket (has grown out from under us).
1506 // All that will happen is possibly dropping an entry
1507 // on the floor or adding a duplicate.
1508 size_t index; //current probe point in the bucket
1509 size_t stride; //amount to step on each successive probe, must be relatively prime wrt the bucket size
1510 size_t mask; //size of bucket - 1
1511 size_t probes; //number probes - 1
1512 Entry* comparer;//used to compare an entry against the sought after key pair
1513};
1514
1515/********************************************************************************************************
1516FastTable is used to implement the buckets of a BucketTable, a bucketized hash table. A FastTable is
1517an array of entries (contents). The first two slots of contents store the size-1 and count of entries
1518actually in the FastTable. Note that the count may be inaccurate and there may be duplicates. Careful
1519attention must be paid to eliminate the need for interlocked or serialized or locked operations in face
1520of concurrency.
1521*/
1522#ifdef _MSC_VER
1523#pragma warning(push)
1524#pragma warning(disable : 4200) // disable zero-sized array warning
1525#endif // _MSC_VER
1526class FastTable
1527{
1528 friend class BucketTable;
1529public:
1530private:
1531 FastTable() { LIMITED_METHOD_CONTRACT; }
1532 ~FastTable() { LIMITED_METHOD_CONTRACT; }
1533
1534 //initialize a prober for the specified keys.
1535 inline BOOL SetUpProber(size_t keyA, size_t keyB, Prober* probe)
1536 {
1537 CONTRACTL {
1538 NOTHROW;
1539 GC_NOTRIGGER;
1540 FORBID_FAULT;
1541 } CONTRACTL_END;
1542
1543 _ASSERTE(probe);
1544 _ASSERTE(contents);
1545 probe->InitProber(keyA, keyB, &contents[0]);
1546 return TRUE;
1547 }
1548 //find the requested entry (keys of prober), if not there return CALL_STUB_EMPTY_ENTRY
1549 size_t Find(Prober* probe);
1550 //add the entry, if it is not already there. Probe is used to search.
1551 //Return the entry actually containted (existing or added)
1552 size_t Add(size_t entry, Prober* probe);
1553 void IncrementCount();
1554
1555 // Create a FastTable with space for numberOfEntries. Please note that this method
1556 // does not throw on OOM. **YOU MUST CHECK FOR NULL RETURN**
1557 static FastTable* MakeTable(size_t numberOfEntries)
1558 {
1559 CONTRACTL {
1560 THROWS;
1561 GC_TRIGGERS;
1562 INJECT_FAULT(COMPlusThrowOM(););
1563 } CONTRACTL_END;
1564
1565 size_t size = CALL_STUB_MIN_ENTRIES;
1566 while (size < numberOfEntries) {size = size<<1;}
1567// if (size == CALL_STUB_MIN_ENTRIES)
1568// size += 3;
1569 size_t* bucket = new size_t[(sizeof(FastTable)/sizeof(size_t))+size+CALL_STUB_FIRST_INDEX];
1570 FastTable* table = new (bucket) FastTable();
1571 table->InitializeContents(size);
1572 return table;
1573 }
1574 //Initialize as empty
1575 void InitializeContents(size_t size)
1576 {
1577 LIMITED_METHOD_CONTRACT;
1578 memset(&contents[0], CALL_STUB_EMPTY_ENTRY, (size+CALL_STUB_FIRST_INDEX)*sizeof(BYTE*));
1579 contents[CALL_STUB_MASK_INDEX] = size-1;
1580 }
1581 inline size_t tableMask() {LIMITED_METHOD_CONTRACT; return (size_t) (contents[CALL_STUB_MASK_INDEX]);}
1582 inline size_t tableSize() {LIMITED_METHOD_CONTRACT; return tableMask()+1;}
1583 inline size_t tableCount() {LIMITED_METHOD_CONTRACT; return (size_t) (contents[CALL_STUB_COUNT_INDEX]);}
1584 inline BOOL isFull()
1585 {
1586 LIMITED_METHOD_CONTRACT;
1587 return (tableCount()+1) * 100 / CALL_STUB_LOAD_FACTOR >= tableSize();
1588 }
1589 //we store (size-1) in bucket[CALL_STUB_MASK_INDEX==0],
1590 //we store the used count in bucket[CALL_STUB_COUNT_INDEX==1],
1591 //we have an unused cell to use as a temp at bucket[CALL_STUB_DEAD_LINK==2],
1592 //and the table starts at bucket[CALL_STUB_FIRST_INDEX==3],
1593 size_t contents[];
1594};
1595#ifdef _MSC_VER
1596#pragma warning(pop)
1597#endif
1598
1599/******************************************************************************************************
1600BucketTable is a bucketized hash table. It uses FastTables for its buckets. The hash tables
1601used by the VirtualCallStubManager are BucketTables. The number of buckets is fixed at the time
1602the table is created. The actual buckets are allocated as needed, and grow as necessary. The reason
1603for using buckets is primarily to reduce the cost of growing, since only a single bucket is actually
1604grown at any given time. Since the hash tables are accessed infrequently, the load factor that
1605controls growth is quite high (90%). Since we use hashing to pick the bucket, and we use hashing to
1606lookup inside the bucket, it is important that the hashing function used here is orthogonal to the ones
1607used in the buckets themselves (see FastTable::FormHash).
1608*/
1609class BucketTable
1610{
1611public:
1612 BucketTable(size_t numberOfBuckets)
1613 {
1614 WRAPPER_NO_CONTRACT;
1615 size_t size = CALL_STUB_MIN_BUCKETS;
1616 while (size < numberOfBuckets) {size = size<<1;}
1617 buckets = AllocateBuckets(size);
1618 // Initialize statistics counters
1619 memset(&stats, 0, sizeof(stats));
1620 }
1621
1622 ~BucketTable()
1623 {
1624 LIMITED_METHOD_CONTRACT;
1625 if(buckets != NULL)
1626 {
1627 size_t size = bucketCount()+CALL_STUB_FIRST_INDEX;
1628 for(size_t ix = CALL_STUB_FIRST_INDEX; ix < size; ix++) delete (FastTable*)(buckets[ix]);
1629 delete buckets;
1630 }
1631 }
1632
1633 //initialize a prober for the specified keys.
1634 BOOL SetUpProber(size_t keyA, size_t keyB, Prober *prober);
1635 //find the requested entry (keys of prober), if not there return CALL_STUB_EMPTY_ENTRY
1636 inline size_t Find(Prober* probe) {WRAPPER_NO_CONTRACT; return probe->Find();}
1637 //add the entry, if it is not already there. Probe is used to search.
1638 size_t Add(size_t entry, Prober* probe);
1639 //reclaim abandoned buckets. Buckets are abaondoned when they need to grow.
1640 //needs to be called inside a gc sync point.
1641 static void Reclaim();
1642
1643 struct
1644 {
1645 UINT32 bucket_space; //# of bytes in caches and tables, not including the stubs themselves
1646 UINT32 bucket_space_dead; //# of bytes of abandoned buckets not yet recycled.
1647 } stats;
1648
1649 void LogStats();
1650
1651private:
1652 inline size_t bucketMask() {LIMITED_METHOD_CONTRACT; return (size_t) (buckets[CALL_STUB_MASK_INDEX]);}
1653 inline size_t bucketCount() {LIMITED_METHOD_CONTRACT; return bucketMask()+1;}
1654 inline size_t ComputeBucketIndex(size_t keyA, size_t keyB)
1655 {
1656 LIMITED_METHOD_CONTRACT;
1657 size_t a = ((keyA>>16) + keyA);
1658 size_t b = ((keyB>>16) ^ keyB);
1659 return CALL_STUB_FIRST_INDEX+(((((a*CALL_STUB_HASH_CONST2)>>5)^((b*CALL_STUB_HASH_CONST1)>>5))+CALL_STUB_HASH_CONST2) & bucketMask());
1660 }
1661 //grows the bucket referenced by probe.
1662 BOOL GetMoreSpace(const Prober* probe);
1663 //creates storage in which to store references to the buckets
1664 static size_t* AllocateBuckets(size_t size)
1665 {
1666 LIMITED_METHOD_CONTRACT;
1667 size_t* buckets = new size_t[size+CALL_STUB_FIRST_INDEX];
1668 if (buckets != NULL)
1669 {
1670 memset(&buckets[0], CALL_STUB_EMPTY_ENTRY, (size+CALL_STUB_FIRST_INDEX)*sizeof(void*));
1671 buckets[CALL_STUB_MASK_INDEX] = size-1;
1672 }
1673 return buckets;
1674 }
1675 inline size_t Read(size_t index)
1676 {
1677 LIMITED_METHOD_CONTRACT;
1678 CONSISTENCY_CHECK(index <= bucketMask()+CALL_STUB_FIRST_INDEX);
1679 return VolatileLoad(&buckets[index]);
1680 }
1681
1682#ifdef _MSC_VER
1683#pragma warning(disable: 4267) //work-around for the compiler
1684#endif
1685 inline void Write(size_t index, size_t value)
1686 {
1687 LIMITED_METHOD_CONTRACT;
1688 CONSISTENCY_CHECK(index <= bucketMask()+CALL_STUB_FIRST_INDEX);
1689 VolatileStore(&buckets[index], value);
1690 }
1691#ifdef _MSC_VER
1692#pragma warning(default: 4267)
1693#endif
1694
1695 // We store (#buckets-1) in bucket[CALL_STUB_MASK_INDEX ==0]
1696 // We have two unused cells at bucket[CALL_STUB_COUNT_INDEX ==1]
1697 // and bucket[CALL_STUB_DEAD_LINK ==2]
1698 // and the table starts at bucket[CALL_STUB_FIRST_INDEX ==3]
1699 // the number of elements is bucket[CALL_STUB_MASK_INDEX]+CALL_STUB_FIRST_INDEX
1700 size_t* buckets;
1701 static FastTable* dead; //linked list head of to be deleted (abandoned) buckets
1702};
1703
1704#endif // !CROSSGEN_COMPILE
1705
1706#endif // !_VIRTUAL_CALL_STUB_H
1707