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 * Generational GC handle manager. Main Entrypoint Layer.
7 *
8 * Implements generic support for external roots into a GC heap.
9 *
10
11 *
12 */
13
14#include "common.h"
15
16#include "gcenv.h"
17
18#include "gc.h"
19#include "gceventstatus.h"
20
21#include "objecthandle.h"
22#include "handletablepriv.h"
23
24#if defined(ENABLE_PERF_COUNTERS) || defined(FEATURE_EVENT_TRACE)
25DWORD g_dwHandles = 0;
26#endif // ENABLE_PERF_COUNTERS || FEATURE_EVENT_TRACE
27
28/****************************************************************************
29 *
30 * FORWARD DECLARATIONS
31 *
32 ****************************************************************************/
33
34#ifdef _DEBUG
35void DEBUG_PostGCScanHandler(HandleTable *pTable, const uint32_t *types, uint32_t typeCount, uint32_t condemned, uint32_t maxgen, ScanCallbackInfo *info);
36void DEBUG_LogScanningStatistics(HandleTable *pTable, uint32_t level);
37#endif
38
39/*--------------------------------------------------------------------------*/
40
41
42
43/****************************************************************************
44 *
45 * HELPER ROUTINES
46 *
47 ****************************************************************************/
48
49/*
50 * Table
51 *
52 * Gets and validates the table pointer from a table handle.
53 *
54 */
55__inline PTR_HandleTable Table(HHANDLETABLE hTable)
56{
57 WRAPPER_NO_CONTRACT;
58 SUPPORTS_DAC;
59
60 // convert the handle to a pointer
61 PTR_HandleTable pTable = (PTR_HandleTable)hTable;
62
63 // sanity
64 _ASSERTE(pTable);
65
66 // return the table pointer
67 return pTable;
68}
69
70/*--------------------------------------------------------------------------*/
71
72
73
74/****************************************************************************
75 *
76 * MAIN ENTRYPOINTS
77 *
78 ****************************************************************************/
79#ifndef DACCESS_COMPILE
80/*
81 * HndCreateHandleTable
82 *
83 * Allocates and initializes a handle table.
84 *
85 */
86HHANDLETABLE HndCreateHandleTable(const uint32_t *pTypeFlags, uint32_t uTypeCount, ADIndex uADIndex)
87{
88 CONTRACTL
89 {
90 NOTHROW;
91 GC_NOTRIGGER;
92 INJECT_FAULT(return NULL);
93 }
94 CONTRACTL_END;
95
96 // sanity
97 _ASSERTE(uTypeCount);
98
99 // verify that we can handle the specified number of types
100 // may need to increase HANDLE_MAX_INTERNAL_TYPES (by 4)
101 _ASSERTE(uTypeCount <= HANDLE_MAX_PUBLIC_TYPES);
102
103 // verify that segment header layout we're using fits expected size
104 _ASSERTE(sizeof(_TableSegmentHeader) <= HANDLE_HEADER_SIZE);
105 // if you hit this then TABLE LAYOUT IS BROKEN
106
107 // compute the size of the handle table allocation
108 uint32_t dwSize = sizeof(HandleTable) + (uTypeCount * sizeof(HandleTypeCache));
109
110 // allocate the table
111 HandleTable *pTable = (HandleTable *) new (nothrow) uint8_t[dwSize];
112 if (pTable == NULL)
113 return NULL;
114
115 memset (pTable, 0, dwSize);
116
117 // allocate the initial handle segment
118 pTable->pSegmentList = SegmentAlloc(pTable);
119
120 // if that failed then we are also out of business
121 if (!pTable->pSegmentList)
122 {
123 // free the table's memory and get out
124 delete [] (uint8_t*)pTable;
125 return NULL;
126 }
127
128 // initialize the table's lock
129 // We need to allow CRST_UNSAFE_SAMELEVEL, because
130 // during AD unload, we need to move some TableSegment from unloaded domain to default domain.
131 // We need to take both locks for the two HandleTable's to avoid racing with concurrent gc thread.
132 if (!pTable->Lock.InitNoThrow(CrstHandleTable, CrstFlags(CRST_REENTRANCY | CRST_UNSAFE_ANYMODE | CRST_DEBUGGER_THREAD | CRST_UNSAFE_SAMELEVEL)))
133 {
134 SegmentFree(pTable->pSegmentList);
135 delete [] (uint8_t*)pTable;
136 return NULL;
137 }
138
139 // remember how many types we are supporting
140 pTable->uTypeCount = uTypeCount;
141
142 // Store user data
143 pTable->uTableIndex = (uint32_t) -1;
144 pTable->uADIndex = uADIndex;
145
146 // loop over various arrays an initialize them
147 uint32_t u;
148
149 // initialize the type flags for the types we were passed
150 for (u = 0; u < uTypeCount; u++)
151 pTable->rgTypeFlags[u] = pTypeFlags[u];
152
153 // preinit the rest to HNDF_NORMAL
154 while (u < HANDLE_MAX_INTERNAL_TYPES)
155 pTable->rgTypeFlags[u++] = HNDF_NORMAL;
156
157 // initialize the main cache
158 for (u = 0; u < uTypeCount; u++)
159 {
160 // at init time, the only non-zero field in a type cache is the free index
161 pTable->rgMainCache[u].lFreeIndex = HANDLES_PER_CACHE_BANK;
162 }
163
164#ifdef _DEBUG
165 // set up scanning stats
166 pTable->_DEBUG_iMaxGen = -1;
167#endif
168
169 // all done - return the newly created table
170 return (HHANDLETABLE)pTable;
171}
172
173
174/*
175 * HndDestroyHandleTable
176 *
177 * Cleans up and frees the specified handle table.
178 *
179 */
180void HndDestroyHandleTable(HHANDLETABLE hTable)
181{
182 WRAPPER_NO_CONTRACT;
183
184 // fetch the handle table pointer
185 HandleTable *pTable = Table(hTable);
186
187 // decrement handle count by number of handles in this table
188 COUNTER_ONLY(GetPerfCounters().m_GC.cHandles -= HndCountHandles(hTable));
189
190 // We are going to free the memory for this HandleTable.
191 // Let us reset the copy in g_pHandleTableArray to NULL.
192 // Otherwise, GC will think this HandleTable is still available.
193
194 // free the lock
195 pTable->Lock.Destroy();
196
197 // fetch the segment list and null out the list pointer
198 TableSegment *pSegment = pTable->pSegmentList;
199 pTable->pSegmentList = NULL;
200
201 // walk the segment list, freeing the segments as we go
202 while (pSegment)
203 {
204 // fetch the next segment
205 TableSegment *pNextSegment = pSegment->pNextSegment;
206
207 // free the current one and advance to the next
208 SegmentFree(pSegment);
209 pSegment = pNextSegment;
210 }
211
212 // free the table's memory
213 delete [] (uint8_t*) pTable;
214}
215/*
216 * HndSetHandleTableIndex
217 *
218 * Sets the index associated with a handle table at creation
219 */
220void HndSetHandleTableIndex(HHANDLETABLE hTable, uint32_t uTableIndex)
221{
222 WRAPPER_NO_CONTRACT;
223
224 // fetch the handle table pointer
225 HandleTable *pTable = Table(hTable);
226
227 pTable->uTableIndex = uTableIndex;
228}
229#endif // !DACCESS_COMPILE
230
231/*
232 * HndGetHandleTableIndex
233 *
234 * Retrieves the index associated with a handle table at creation
235 */
236uint32_t HndGetHandleTableIndex(HHANDLETABLE hTable)
237{
238 WRAPPER_NO_CONTRACT;
239
240 // fetch the handle table pointer
241 HandleTable *pTable = Table(hTable);
242
243 _ASSERTE (pTable->uTableIndex != (uint32_t) -1); // We have not set uTableIndex yet.
244 return pTable->uTableIndex;
245}
246
247/*
248 * HndGetHandleTableIndex
249 *
250 * Retrieves the AppDomain index associated with a handle table at creation
251 */
252ADIndex HndGetHandleTableADIndex(HHANDLETABLE hTable)
253{
254 WRAPPER_NO_CONTRACT;
255
256 // fetch the handle table pointer
257 HandleTable *pTable = Table(hTable);
258
259 return pTable->uADIndex;
260}
261
262/*
263 * HndGetHandleTableIndex
264 *
265 * Retrieves the AppDomain index associated with a handle table at creation
266 */
267GC_DAC_VISIBLE
268ADIndex HndGetHandleADIndex(OBJECTHANDLE handle)
269{
270 WRAPPER_NO_CONTRACT;
271 SUPPORTS_DAC;
272
273 // fetch the handle table pointer
274 HandleTable *pTable = Table(HndGetHandleTable(handle));
275
276 return pTable->uADIndex;
277}
278
279#ifndef DACCESS_COMPILE
280/*
281 * HndCreateHandle
282 *
283 * Entrypoint for allocating an individual handle.
284 *
285 */
286OBJECTHANDLE HndCreateHandle(HHANDLETABLE hTable, uint32_t uType, OBJECTREF object, uintptr_t lExtraInfo)
287{
288 CONTRACTL
289 {
290 NOTHROW;
291 GC_NOTRIGGER;
292 if (object != NULL)
293 {
294 MODE_COOPERATIVE;
295 }
296 else
297 {
298 MODE_ANY;
299 }
300 SO_INTOLERANT;
301 }
302 CONTRACTL_END;
303
304 // If we are creating a variable-strength handle, verify that the
305 // requested variable handle type is valid.
306 _ASSERTE(uType != HNDTYPE_VARIABLE || IS_VALID_VHT_VALUE(lExtraInfo));
307
308 VALIDATEOBJECTREF(object);
309
310 // fetch the handle table pointer
311 HandleTable *pTable = Table(hTable);
312
313 // sanity check the type index
314 _ASSERTE(uType < pTable->uTypeCount);
315
316 // get a handle from the table's cache
317 OBJECTHANDLE handle = TableAllocSingleHandleFromCache(pTable, uType);
318
319 // did the allocation succeed?
320 if (!handle)
321 {
322 return NULL;
323 }
324
325#ifdef DEBUG_DestroyedHandleValue
326 if (*(_UNCHECKED_OBJECTREF *)handle == DEBUG_DestroyedHandleValue)
327 *(_UNCHECKED_OBJECTREF *)handle = NULL;
328#endif
329
330 // yep - the handle better not point at anything yet
331 _ASSERTE(*(_UNCHECKED_OBJECTREF *)handle == NULL);
332
333 // we are not holding the lock - check to see if there is nonzero extra info
334 if (lExtraInfo)
335 {
336 // initialize the user data BEFORE assigning the referent
337 // this ensures proper behavior if we are currently scanning
338 HandleQuickSetUserData(handle, lExtraInfo);
339 }
340
341#if defined(ENABLE_PERF_COUNTERS) || defined(FEATURE_EVENT_TRACE)
342 g_dwHandles++;
343#endif // defined(ENABLE_PERF_COUNTERS) || defined(FEATURE_EVENT_TRACE)
344
345 // store the reference
346 HndAssignHandle(handle, object);
347 STRESS_LOG2(LF_GC, LL_INFO1000, "CreateHandle: %p, type=%d\n", handle, uType);
348
349 // return the result
350 return handle;
351}
352#endif // !DACCESS_COMPILE
353
354#ifdef _DEBUG
355void ValidateFetchObjrefForHandle(OBJECTREF objref, ADIndex appDomainIndex)
356{
357 STATIC_CONTRACT_NOTHROW;
358 STATIC_CONTRACT_GC_NOTRIGGER;
359 STATIC_CONTRACT_SO_TOLERANT;
360 STATIC_CONTRACT_MODE_COOPERATIVE;
361 STATIC_CONTRACT_DEBUG_ONLY;
362
363 BEGIN_DEBUG_ONLY_CODE;
364 VALIDATEOBJECTREF (objref);
365
366#ifndef DACCESS_COMPILE
367 _ASSERTE(GCToEEInterface::AppDomainCanAccessHandleTable(appDomainIndex.m_dwIndex));
368#endif // DACCESS_COMPILE
369
370 END_DEBUG_ONLY_CODE;
371}
372
373void ValidateAssignObjrefForHandle(OBJECTREF objref, ADIndex appDomainIndex)
374{
375 STATIC_CONTRACT_NOTHROW;
376 STATIC_CONTRACT_GC_NOTRIGGER;
377 STATIC_CONTRACT_SO_TOLERANT;
378 STATIC_CONTRACT_MODE_COOPERATIVE;
379 STATIC_CONTRACT_DEBUG_ONLY;
380
381 BEGIN_DEBUG_ONLY_CODE;
382
383 VALIDATEOBJECTREF (objref);
384
385#ifndef DACCESS_COMPILE
386 _ASSERTE(GCToEEInterface::AppDomainCanAccessHandleTable(appDomainIndex.m_dwIndex));
387#endif // DACCESS_COMPILE
388 END_DEBUG_ONLY_CODE;
389}
390
391void ValidateAppDomainForHandle(OBJECTHANDLE handle)
392{
393 STATIC_CONTRACT_DEBUG_ONLY;
394 STATIC_CONTRACT_NOTHROW;
395
396#ifdef DEBUG_DestroyedHandleValue
397 // Verify that we are not trying to access freed handle.
398 _ASSERTE("Attempt to access destroyed handle." && *(_UNCHECKED_OBJECTREF *)handle != DEBUG_DestroyedHandleValue);
399#endif
400#ifdef DACCESS_COMPILE
401 UNREFERENCED_PARAMETER(handle);
402#else
403 BEGIN_DEBUG_ONLY_CODE;
404 ADIndex id = HndGetHandleADIndex(handle);
405 ADIndex unloadingDomain(GCToEEInterface::GetIndexOfAppDomainBeingUnloaded());
406 if (unloadingDomain != id)
407 {
408 return;
409 }
410 if (GCToEEInterface::AppDomainCanAccessHandleTable(unloadingDomain.m_dwIndex))
411 {
412 return;
413 }
414 _ASSERTE (!"Access to a handle in unloaded domain is not allowed");
415 END_DEBUG_ONLY_CODE;
416#endif // !DACCESS_COMPILE
417}
418#endif
419
420
421#ifndef DACCESS_COMPILE
422/*
423 * HndDestroyHandle
424 *
425 * Entrypoint for freeing an individual handle.
426 *
427 */
428void HndDestroyHandle(HHANDLETABLE hTable, uint32_t uType, OBJECTHANDLE handle)
429{
430 CONTRACTL
431 {
432 NOTHROW;
433 GC_NOTRIGGER;
434 MODE_ANY;
435 SO_TOLERANT;
436 CAN_TAKE_LOCK; // because of TableFreeSingleHandleToCache
437 }
438 CONTRACTL_END;
439
440 STRESS_LOG2(LF_GC, LL_INFO1000, "DestroyHandle: *%p->%p\n", handle, *(_UNCHECKED_OBJECTREF *)handle);
441
442 FIRE_EVENT(DestroyGCHandle, (void *)handle);
443 FIRE_EVENT(PrvDestroyGCHandle, (void *)handle);
444
445 // sanity check handle we are being asked to free
446 _ASSERTE(handle);
447
448#ifdef _DEBUG
449 ValidateAppDomainForHandle(handle);
450#endif
451
452 // fetch the handle table pointer
453 HandleTable *pTable = Table(hTable);
454
455 // sanity check the type index
456 _ASSERTE(uType < pTable->uTypeCount);
457
458 _ASSERTE(HandleFetchType(handle) == uType);
459
460 // return the handle to the table's cache
461 TableFreeSingleHandleToCache(pTable, uType, handle);
462
463#if defined(ENABLE_PERF_COUNTERS) || defined(FEATURE_EVENT_TRACE)
464 g_dwHandles--;
465#endif // defined(ENABLE_PERF_COUNTERS) || defined(FEATURE_EVENT_TRACE)
466}
467
468
469/*
470 * HndDestroyHandleOfUnknownType
471 *
472 * Entrypoint for freeing an individual handle whose type is unknown.
473 *
474 */
475void HndDestroyHandleOfUnknownType(HHANDLETABLE hTable, OBJECTHANDLE handle)
476{
477 CONTRACTL
478 {
479 NOTHROW;
480 GC_NOTRIGGER;
481 SO_TOLERANT;
482 MODE_ANY;
483 }
484 CONTRACTL_END;
485
486 // sanity check handle we are being asked to free
487 _ASSERTE(handle);
488
489#ifdef FEATURE_COMINTEROP
490 // If we're being asked to destroy a WinRT weak handle, that will cause a leak
491 // of the IWeakReference* that it holds in its extra data. Instead of using this
492 // API use DestroyWinRTWeakHandle instead.
493 _ASSERTE(HandleFetchType(handle) != HNDTYPE_WEAK_WINRT);
494#endif // FEATURE_COMINTEROP
495
496 // fetch the type and then free normally
497 HndDestroyHandle(hTable, HandleFetchType(handle), handle);
498}
499
500/*
501 * HndSetHandleExtraInfo
502 *
503 * Stores owner data with handle.
504 *
505 */
506void HndSetHandleExtraInfo(OBJECTHANDLE handle, uint32_t uType, uintptr_t lExtraInfo)
507{
508 WRAPPER_NO_CONTRACT;
509
510 // fetch the user data slot for this handle if we have the right type
511 uintptr_t *pUserData = HandleValidateAndFetchUserDataPointer(handle, uType);
512
513 // is there a slot?
514 if (pUserData)
515 {
516 // yes - store the info
517 *pUserData = lExtraInfo;
518 }
519}
520
521/*
522* HndCompareExchangeHandleExtraInfo
523*
524* Stores owner data with handle.
525*
526*/
527uintptr_t HndCompareExchangeHandleExtraInfo(OBJECTHANDLE handle, uint32_t uType, uintptr_t lOldExtraInfo, uintptr_t lNewExtraInfo)
528{
529 WRAPPER_NO_CONTRACT;
530
531 // fetch the user data slot for this handle if we have the right type
532 uintptr_t *pUserData = HandleValidateAndFetchUserDataPointer(handle, uType);
533
534 // is there a slot?
535 if (pUserData)
536 {
537 // yes - attempt to store the info
538 return (uintptr_t)Interlocked::CompareExchangePointer((void**)pUserData, (void*)lNewExtraInfo, (void*)lOldExtraInfo);
539 }
540
541 _ASSERTE(!"Shouldn't be trying to call HndCompareExchangeHandleExtraInfo on handle types without extra info");
542 return (uintptr_t)NULL;
543}
544#endif // !DACCESS_COMPILE
545
546/*
547 * HndGetHandleExtraInfo
548 *
549 * Retrieves owner data from handle.
550 *
551 */
552GC_DAC_VISIBLE
553uintptr_t HndGetHandleExtraInfo(OBJECTHANDLE handle)
554{
555 WRAPPER_NO_CONTRACT;
556
557 // assume zero until we actually get it
558 uintptr_t lExtraInfo = 0L;
559
560 // fetch the user data slot for this handle
561 PTR_uintptr_t pUserData = HandleQuickFetchUserDataPointer(handle);
562
563 // if we did then copy the value
564 if (pUserData)
565 {
566 lExtraInfo = *(pUserData);
567 }
568
569 // return the value to our caller
570 return lExtraInfo;
571}
572
573/*
574 * HndGetHandleTable
575 *
576 * Returns the containing table of a handle.
577 *
578 */
579HHANDLETABLE HndGetHandleTable(OBJECTHANDLE handle)
580{
581 WRAPPER_NO_CONTRACT;
582 SUPPORTS_DAC;
583
584 PTR_HandleTable pTable = HandleFetchHandleTable(handle);
585
586 return (HHANDLETABLE)pTable;
587}
588
589void HndLogSetEvent(OBJECTHANDLE handle, _UNCHECKED_OBJECTREF value)
590{
591 STATIC_CONTRACT_NOTHROW;
592 STATIC_CONTRACT_GC_NOTRIGGER;
593 STATIC_CONTRACT_SO_TOLERANT;
594 STATIC_CONTRACT_MODE_COOPERATIVE;
595
596#if !defined(DACCESS_COMPILE) && defined(FEATURE_EVENT_TRACE)
597 if (EVENT_ENABLED(SetGCHandle) || EVENT_ENABLED(PrvSetGCHandle))
598 {
599 uint32_t hndType = HandleFetchType(handle);
600 ADIndex appDomainIndex = HndGetHandleADIndex(handle);
601 void* pAppDomain = GCToEEInterface::GetAppDomainAtIndex(appDomainIndex.m_dwIndex);
602 uint32_t generation = value != 0 ? g_theGCHeap->WhichGeneration(value) : 0;
603 FIRE_EVENT(SetGCHandle, (void *)handle, (void *)value, hndType, generation, (uint64_t)pAppDomain);
604 FIRE_EVENT(PrvSetGCHandle, (void *) handle, (void *)value, hndType, generation, (uint64_t)pAppDomain);
605
606 // Also fire the things pinned by Async pinned handles
607 if (hndType == HNDTYPE_ASYNCPINNED)
608 {
609 // the closure passed to "WalkOverlappedObject" is not permitted to implicitly
610 // capture any variables in this scope, since WalkForOverlappedObject takes a bare
611 // function pointer and context pointer as arguments. We can still /explicitly/
612 // close over values in this scope by doing what the compiler would do and introduce
613 // a structure that contains all of the things we closed over, while passing a pointer
614 // to this structure as our closure's context pointer.
615 struct ClosureCapture
616 {
617 void* pAppDomain;
618 Object* overlapped;
619 };
620
621 ClosureCapture captured;
622 captured.pAppDomain = pAppDomain;
623 captured.overlapped = value;
624 GCToEEInterface::WalkAsyncPinned(value, &captured, [](Object*, Object* to, void* ctx)
625 {
626 ClosureCapture* captured = reinterpret_cast<ClosureCapture*>(ctx);
627 uint32_t generation = to != nullptr ? g_theGCHeap->WhichGeneration(to) : 0;
628 FIRE_EVENT(SetGCHandle, (void *)captured->overlapped, (void *)to, HNDTYPE_PINNED, generation, (uint64_t)captured->pAppDomain);
629 });
630 }
631 }
632#else
633 UNREFERENCED_PARAMETER(handle);
634 UNREFERENCED_PARAMETER(value);
635#endif
636}
637
638#ifndef DACCESS_COMPILE
639/*
640 * HndWriteBarrier
641 *
642 * Resets the generation number for the handle's clump to zero.
643 *
644 */
645void HndWriteBarrier(OBJECTHANDLE handle, OBJECTREF objref)
646{
647 STATIC_CONTRACT_NOTHROW;
648 STATIC_CONTRACT_GC_NOTRIGGER;
649 STATIC_CONTRACT_SO_TOLERANT;
650 STATIC_CONTRACT_MODE_COOPERATIVE;
651
652 // unwrap the objectref we were given
653 _UNCHECKED_OBJECTREF value = OBJECTREF_TO_UNCHECKED_OBJECTREF(objref);
654
655 _ASSERTE (objref != NULL);
656
657 // find the write barrier for this handle
658 uint8_t *barrier = (uint8_t *)((uintptr_t)handle & HANDLE_SEGMENT_ALIGN_MASK);
659
660 // sanity
661 _ASSERTE(barrier);
662
663 // find the offset of this handle into the segment
664 uintptr_t offset = (uintptr_t)handle & HANDLE_SEGMENT_CONTENT_MASK;
665
666 // make sure it is in the handle area and not the header
667 _ASSERTE(offset >= HANDLE_HEADER_SIZE);
668
669 // compute the clump index for this handle
670 offset = (offset - HANDLE_HEADER_SIZE) / (HANDLE_SIZE * HANDLE_HANDLES_PER_CLUMP);
671
672 // Be careful to read and write the age byte via volatile operations. Otherwise the compiler has been
673 // observed to translate the read + conditional write sequence below into an unconditional read/write
674 // (utilizing a conditional register move to determine whether the write is an update or simply writes
675 // back what was read). This is a legal transformation for non-volatile accesses but obviously leads to a
676 // race condition where we can lose an update (see the comment below for the race condition).
677 volatile uint8_t * pClumpAge = barrier + offset;
678
679 // if this age is smaller than age of the clump, update the clump age
680 if (*pClumpAge != 0) // Perf optimization: if clumpAge is 0, nothing more to do
681 {
682 // find out generation
683 int generation = g_theGCHeap->WhichGeneration(value);
684 uint32_t uType = HandleFetchType(handle);
685
686 //OverlappedData need special treatment: because all user data pointed by it needs to be reported by this handle,
687 //its age is consider to be min age of the user data, to be simple, we just make it 0
688 if (uType == HNDTYPE_ASYNCPINNED)
689 {
690 generation = 0;
691 }
692
693 if (uType == HNDTYPE_DEPENDENT)
694 {
695 generation = 0;
696 }
697
698 if (*pClumpAge > (uint8_t) generation)
699 {
700 // We have to be careful here. HndWriteBarrier is not under any synchronization
701 // Consider the scenario where 2 threads are hitting the line below at the same
702 // time. Only one will win. If the winner has an older age than the loser, we
703 // just created a potential GC hole (The clump will not be reporting the
704 // youngest handle in the clump, thus GC may skip the clump). To fix this
705 // we just set the clump age to 0, which means that whoever wins the race
706 // results are the same, as GC will always look at the clump
707 *pClumpAge = (uint8_t)0;
708 }
709 }
710}
711#endif // DACCESS_COMPILE
712
713/*
714 * HndEnumHandles
715 *
716 * Enumerates all handles of the specified type in the handle table.
717 *
718 * This entrypoint is provided for utility code (debugger support etc) that
719 * needs to enumerate all roots in the handle table.
720 *
721 */
722GC_DAC_VISIBLE_NO_MANGLE
723void HndEnumHandles(HHANDLETABLE hTable, const uint32_t *puType, uint32_t uTypeCount,
724 HANDLESCANPROC pfnEnum, uintptr_t lParam1, uintptr_t lParam2, bool fAsync)
725{
726 WRAPPER_NO_CONTRACT;
727
728 // fetch the handle table pointer
729 PTR_HandleTable pTable = Table(hTable);
730
731 // per-block scanning callback
732 BLOCKSCANPROC pfnBlock;
733
734 // do we need to support user data?
735 BOOL fEnumUserData = TypesRequireUserDataScanning(pTable, puType, uTypeCount);
736
737 if (fEnumUserData)
738 {
739 // scan all handles with user data
740 pfnBlock = BlockScanBlocksWithUserData;
741 }
742 else
743 {
744 // scan all handles without user data
745 pfnBlock = BlockScanBlocksWithoutUserData;
746 }
747
748 // set up parameters for handle enumeration
749 ScanCallbackInfo info;
750
751 info.uFlags = (fAsync? HNDGCF_ASYNC : HNDGCF_NORMAL);
752 info.fEnumUserData = fEnumUserData;
753 info.dwAgeMask = 0;
754 info.pCurrentSegment = NULL;
755 info.pfnScan = pfnEnum;
756 info.param1 = lParam1;
757 info.param2 = lParam2;
758
759 // choose a scanning method based on the async flag
760 TABLESCANPROC pfnScanTable = TableScanHandles;
761 if (fAsync)
762 pfnScanTable = xxxTableScanHandlesAsync;
763
764 {
765 // acquire the handle manager lock
766 CrstHolderWithState ch(&pTable->Lock);
767
768 // scan the table
769 pfnScanTable(pTable, puType, uTypeCount, FullSegmentIterator, pfnBlock, &info, &ch);
770 }
771}
772
773/*
774 * HndScanHandlesForGC
775 *
776 * Multiple type scanning entrypoint for GC.
777 *
778 * This entrypoint is provided for GC-time scnas of the handle table ONLY. It
779 * enables ephemeral scanning of the table, and optionally ages the write barrier
780 * as it scans.
781 *
782 */
783GC_DAC_VISIBLE_NO_MANGLE
784void HndScanHandlesForGC(HHANDLETABLE hTable, HANDLESCANPROC scanProc, uintptr_t param1, uintptr_t param2,
785 const uint32_t *types, uint32_t typeCount, uint32_t condemned, uint32_t maxgen, uint32_t flags)
786{
787 WRAPPER_NO_CONTRACT;
788
789 // fetch the table pointer
790 PTR_HandleTable pTable = Table(hTable);
791
792 // per-segment and per-block callbacks
793 SEGMENTITERATOR pfnSegment;
794 BLOCKSCANPROC pfnBlock = NULL;
795
796 // do we need to support user data?
797 BOOL enumUserData =
798 ((flags & HNDGCF_EXTRAINFO) &&
799 TypesRequireUserDataScanning(pTable, types, typeCount));
800
801 // what type of GC are we performing?
802 if (condemned >= maxgen)
803 {
804 // full GC - use our full-service segment iterator
805 pfnSegment = FullSegmentIterator;
806
807 // see if there is a callback
808 if (scanProc)
809 {
810 // do we need to scan blocks with user data?
811 if (enumUserData)
812 {
813 // scan all with user data
814 pfnBlock = BlockScanBlocksWithUserData;
815 }
816 else
817 {
818 // scan all without user data
819 pfnBlock = BlockScanBlocksWithoutUserData;
820 }
821 }
822 else if (flags & HNDGCF_AGE)
823 {
824 // there is only aging to do
825 pfnBlock = BlockAgeBlocks;
826 }
827 }
828 else
829 {
830 // this is an ephemeral GC - is it g0?
831 if (condemned == 0)
832 {
833 // yes - do bare-bones enumeration
834 pfnSegment = QuickSegmentIterator;
835 }
836 else
837 {
838 // no - do normal enumeration
839 pfnSegment = StandardSegmentIterator;
840 }
841
842 // see if there is a callback
843 if (scanProc)
844 {
845 // there is a scan callback - scan the condemned generation
846 pfnBlock = BlockScanBlocksEphemeral;
847 }
848#ifndef DACCESS_COMPILE
849 else if (flags & HNDGCF_AGE)
850 {
851 // there is only aging to do
852 pfnBlock = BlockAgeBlocksEphemeral;
853 }
854#endif
855 }
856
857 // set up parameters for scan callbacks
858 ScanCallbackInfo info;
859
860 info.uFlags = flags;
861 info.fEnumUserData = enumUserData;
862 info.dwAgeMask = BuildAgeMask(condemned, maxgen);
863 info.pCurrentSegment = NULL;
864 info.pfnScan = scanProc;
865 info.param1 = param1;
866 info.param2 = param2;
867
868#if defined(_DEBUG) && !defined(DACCESS_COMPILE)
869 info.DEBUG_BlocksScanned = 0;
870 info.DEBUG_BlocksScannedNonTrivially = 0;
871 info.DEBUG_HandleSlotsScanned = 0;
872 info.DEBUG_HandlesActuallyScanned = 0;
873#endif
874
875 // choose a scanning method based on the async flag
876 TABLESCANPROC pfnScanTable = TableScanHandles;
877 if (flags & HNDGCF_ASYNC)
878 {
879 pfnScanTable = xxxTableScanHandlesAsync;
880 }
881
882 {
883 // lock the table down for concurrent GC only
884 CrstHolderWithState ch(&pTable->Lock, (flags & HNDGCF_ASYNC) != 0);
885
886 // perform the scan
887 pfnScanTable(pTable, types, typeCount, pfnSegment, pfnBlock, &info, &ch);
888
889#if defined(_DEBUG) && !defined(DACCESS_COMPILE)
890 // update our scanning statistics for this generation
891 DEBUG_PostGCScanHandler(pTable, types, typeCount, condemned, maxgen, &info);
892 #endif
893 }
894}
895
896#ifndef DACCESS_COMPILE
897
898
899/*
900 * HndResetAgeMap
901 *
902 * Service to forceably reset the age map for a set of handles.
903 *
904 * Provided for GC-time resetting the handle table's write barrier. This is not
905 * normally advisable, as it increases the amount of work that will be done in
906 * subsequent scans. Under some circumstances, however, this is precisely what is
907 * desired. Generally this entrypoint should only be used under some exceptional
908 * condition during garbage collection, like objects being demoted from a higher
909 * generation to a lower one.
910 *
911 */
912void HndResetAgeMap(HHANDLETABLE hTable, const uint32_t *types, uint32_t typeCount, uint32_t condemned, uint32_t maxgen, uint32_t flags)
913{
914 WRAPPER_NO_CONTRACT;
915
916 // fetch the table pointer
917 HandleTable *pTable = Table(hTable);
918
919 // set up parameters for scan callbacks
920 ScanCallbackInfo info;
921
922 info.uFlags = flags;
923 info.fEnumUserData = FALSE;
924 info.dwAgeMask = BuildAgeMask(condemned, maxgen);
925 info.pCurrentSegment = NULL;
926 info.pfnScan = NULL;
927 info.param1 = 0;
928 info.param2 = 0;
929
930 {
931 // lock the table down
932 CrstHolderWithState ch(&pTable->Lock);
933
934 // perform the scan
935 TableScanHandles(pTable, types, typeCount, QuickSegmentIterator, BlockResetAgeMapForBlocks, &info, &ch);
936 }
937}
938
939
940/*
941 * HndVerifyTable
942 *
943 * Service to check the correctness of the handle table for a set of handles
944 *
945 * Provided for checking the correctness of handle table and the gc.
946 * Will validate that each handle points to a valid object.
947 * Will also validate that the generation of the handle is <= generation of the object.
948 * Cannot have == because the handle table only remembers the generation for a group of
949 * 16 handles.
950 *
951 */
952void HndVerifyTable(HHANDLETABLE hTable, const uint32_t *types, uint32_t typeCount, uint32_t condemned, uint32_t maxgen, uint32_t flags)
953{
954 WRAPPER_NO_CONTRACT;
955
956 // fetch the table pointer
957 HandleTable *pTable = Table(hTable);
958
959 // set up parameters for scan callbacks
960 ScanCallbackInfo info;
961
962 info.uFlags = flags;
963 info.fEnumUserData = FALSE;
964 info.dwAgeMask = BuildAgeMask(condemned, maxgen);
965 info.pCurrentSegment = NULL;
966 info.pfnScan = NULL;
967 info.param1 = 0;
968 info.param2 = 0;
969
970 {
971 // lock the table down
972 CrstHolderWithState ch(&pTable->Lock);
973
974 // perform the scan
975 TableScanHandles(pTable, types, typeCount, QuickSegmentIterator, BlockVerifyAgeMapForBlocks, &info, &ch);
976 }
977}
978
979
980/*
981 * HndNotifyGcCycleComplete
982 *
983 * Informs the handle table that a GC has completed.
984 *
985 */
986void HndNotifyGcCycleComplete(HHANDLETABLE hTable, uint32_t condemned, uint32_t maxgen)
987{
988#ifdef _DEBUG
989 WRAPPER_NO_CONTRACT;
990
991 // fetch the handle table pointer
992 HandleTable *pTable = Table(hTable);
993
994 {
995 // lock the table down
996 CrstHolder ch(&pTable->Lock);
997
998 // if this was a full GC then dump a cumulative log of scanning stats
999 if (condemned >= maxgen)
1000 DEBUG_LogScanningStatistics(pTable, LL_INFO10);
1001 }
1002#else
1003 LIMITED_METHOD_CONTRACT;
1004 UNREFERENCED_PARAMETER(hTable);
1005 UNREFERENCED_PARAMETER(condemned);
1006 UNREFERENCED_PARAMETER(maxgen);
1007#endif
1008}
1009
1010extern int getNumberOfSlots();
1011
1012
1013/*
1014 * HndCountHandles
1015 *
1016 * Counts the number of handles owned by the handle table that are marked as
1017 * "used" that are not currently residing in the handle table's cache.
1018 *
1019 * Provided to compute the correct value for the GC Handle perfcounter.
1020 * The caller is responsible for acquiring the handle table's lock if
1021 * it is necessary.
1022 *
1023 */
1024uint32_t HndCountHandles(HHANDLETABLE hTable)
1025{
1026 WRAPPER_NO_CONTRACT;
1027 // fetch the handle table pointer
1028 HandleTable *pTable = Table(hTable);
1029
1030 // initialize the count of handles in the cache to 0
1031 uint32_t uCacheCount = 0;
1032
1033 // fetch the count of handles marked as "used"
1034 uint32_t uCount = pTable->dwCount;
1035
1036 // loop through the main cache for each handle type
1037 HandleTypeCache *pCache = pTable->rgMainCache;
1038 HandleTypeCache *pCacheEnd = pCache + pTable->uTypeCount;
1039 for (; pCache != pCacheEnd; ++pCache)
1040 {
1041 // get relevant indexes for the reserve bank and the free bank
1042 int32_t lFreeIndex = pCache->lFreeIndex;
1043 int32_t lReserveIndex = pCache->lReserveIndex;
1044
1045 // clamp the min free index and min reserve index to be non-negative;
1046 // this is necessary since interlocked operations can set these variables
1047 // to negative values, and once negative they stay negative until the
1048 // cache is rebalanced
1049 if (lFreeIndex < 0) lFreeIndex = 0;
1050 if (lReserveIndex < 0) lReserveIndex = 0;
1051
1052 // compute the number of handles
1053 uint32_t uHandleCount = (uint32_t)lReserveIndex + (HANDLES_PER_CACHE_BANK - (uint32_t)lFreeIndex);
1054
1055 // add the number of handles to the total handle count and update
1056 // dwCount in this HandleTable
1057 uCacheCount += uHandleCount;
1058 }
1059
1060 // it is not necessary to have the lock while reading the quick cache;
1061 // loop through the quick cache for each handle type
1062 OBJECTHANDLE * pQuickCache = pTable->rgQuickCache;
1063 OBJECTHANDLE * pQuickCacheEnd = pQuickCache + HANDLE_MAX_INTERNAL_TYPES;
1064 for (; pQuickCache != pQuickCacheEnd; ++pQuickCache)
1065 if (*pQuickCache)
1066 ++uCacheCount;
1067
1068 // return the number of handles marked as "used" that are not
1069 // residing in the cache
1070 return (uCount - uCacheCount);
1071}
1072
1073
1074/*
1075 * HndCountAllHandles
1076 *
1077 * Counts the total number of handles that are marked as "used" that are not
1078 * currently residing in some handle table's cache.
1079 *
1080 * Provided to compute the correct value for the GC Handle perfcounter.
1081 * The 'fUseLocks' flag specifies whether to acquire each handle table's lock
1082 * while its handles are being counted.
1083 *
1084 */
1085uint32_t HndCountAllHandles(BOOL fUseLocks)
1086{
1087 uint32_t uCount = 0;
1088 int offset = 0;
1089
1090 // get number of HandleTables per HandleTableBucket
1091 int n_slots = getNumberOfSlots();
1092
1093 // fetch the pointer to the head of the list
1094 struct HandleTableMap * walk = &g_HandleTableMap;
1095
1096 // walk the list
1097 while (walk)
1098 {
1099 int nextOffset = walk->dwMaxIndex;
1100 int max = nextOffset - offset;
1101 PTR_PTR_HandleTableBucket pBucket = walk->pBuckets;
1102 PTR_PTR_HandleTableBucket pLastBucket = pBucket + max;
1103
1104 // loop through each slot in this node
1105 for (; pBucket != pLastBucket; ++pBucket)
1106 {
1107 // if there is a HandleTableBucket in this slot
1108 if (*pBucket)
1109 {
1110 // loop through the HandleTables inside this HandleTableBucket,
1111 // and accumulate the handle count of each HandleTable
1112 HHANDLETABLE * pTable = (*pBucket)->pTable;
1113 HHANDLETABLE * pLastTable = pTable + n_slots;
1114
1115 // if the 'fUseLocks' flag is set, acquire the lock for this handle table before
1116 // calling HndCountHandles() - this will prevent dwCount from being modified and
1117 // it will also prevent any of the main caches from being rebalanced
1118 if (fUseLocks)
1119 for (; pTable != pLastTable; ++pTable)
1120 {
1121 CrstHolder ch(&(Table(*pTable)->Lock));
1122 uCount += HndCountHandles(*pTable);
1123 }
1124 else
1125 for (; pTable != pLastTable; ++pTable)
1126 uCount += HndCountHandles(*pTable);
1127 }
1128 }
1129
1130 offset = nextOffset;
1131 walk = walk->pNext;
1132 }
1133
1134 //return the total number of handles in all HandleTables
1135 return uCount;
1136}
1137
1138BOOL Ref_HandleAsyncPinHandles(async_pin_enum_fn asyncPinCallback, void* context)
1139{
1140 CONTRACTL
1141 {
1142 NOTHROW;
1143 GC_NOTRIGGER;
1144 }
1145 CONTRACTL_END;
1146
1147 AsyncPinCallbackContext callbackCtx(asyncPinCallback, context);
1148 HandleTableBucket *pBucket = g_HandleTableMap.pBuckets[0];
1149 BOOL result = FALSE;
1150 int limit = getNumberOfSlots();
1151 for (int n = 0; n < limit; n ++ )
1152 {
1153 if (TableHandleAsyncPinHandles(Table(pBucket->pTable[n]), callbackCtx))
1154 {
1155 result = TRUE;
1156 }
1157 }
1158
1159 return result;
1160}
1161
1162void Ref_RelocateAsyncPinHandles(HandleTableBucket *pSource,
1163 HandleTableBucket *pTarget,
1164 void (*clearIfComplete)(Object* object),
1165 void (*setHandle)(Object* object, OBJECTHANDLE handle))
1166{
1167 CONTRACTL
1168 {
1169 NOTHROW;
1170 GC_TRIGGERS;
1171 }
1172 CONTRACTL_END;
1173
1174 int limit = getNumberOfSlots();
1175 for (int n = 0; n < limit; n ++ )
1176 {
1177 TableRelocateAsyncPinHandles(Table(pSource->pTable[n]), Table(pTarget->pTable[n]), clearIfComplete, setHandle);
1178 }
1179}
1180
1181/*--------------------------------------------------------------------------*/
1182
1183
1184
1185/****************************************************************************
1186 *
1187 * DEBUG SCANNING STATISTICS
1188 *
1189 ****************************************************************************/
1190#ifdef _DEBUG
1191
1192void DEBUG_PostGCScanHandler(HandleTable *pTable, const uint32_t *types, uint32_t typeCount, uint32_t condemned, uint32_t maxgen, ScanCallbackInfo *info)
1193{
1194 LIMITED_METHOD_CONTRACT;
1195 UNREFERENCED_PARAMETER(types);
1196
1197 // looks like the GC supports more generations than we expected
1198 _ASSERTE(condemned < MAXSTATGEN);
1199
1200 // remember the highest generation we've seen
1201 if (pTable->_DEBUG_iMaxGen < (int)condemned)
1202 pTable->_DEBUG_iMaxGen = (int)condemned;
1203
1204 // update the statistics
1205 pTable->_DEBUG_TotalBlocksScanned [condemned] += info->DEBUG_BlocksScanned;
1206 pTable->_DEBUG_TotalBlocksScannedNonTrivially [condemned] += info->DEBUG_BlocksScannedNonTrivially;
1207 pTable->_DEBUG_TotalHandleSlotsScanned [condemned] += info->DEBUG_HandleSlotsScanned;
1208 pTable->_DEBUG_TotalHandlesActuallyScanned [condemned] += info->DEBUG_HandlesActuallyScanned;
1209
1210 // if this is an ephemeral GC then dump ephemeral stats for this scan right now
1211 if (condemned < maxgen)
1212 {
1213 // dump a header for the stats with the condemned generation number
1214 LOG((LF_GC, LL_INFO1000, "--------------------------------------------------------------\n"));
1215 LOG((LF_GC, LL_INFO1000, "Ephemeral Handle Scan Summary:\n"));
1216 LOG((LF_GC, LL_INFO1000, " Generation = %u\n", condemned));
1217
1218 // dump the handle types we were asked to scan
1219 LOG((LF_GC, LL_INFO1000, " Handle Type(s) = %u", *types));
1220 for (uint32_t u = 1; u < typeCount; u++)
1221 LOG((LF_GC, LL_INFO1000, ",%u", types[u]));
1222 LOG((LF_GC, LL_INFO1000, "\n"));
1223
1224 // dump the number of blocks and slots we scanned
1225 uint32_t blockHandles = info->DEBUG_BlocksScanned * HANDLE_HANDLES_PER_BLOCK;
1226 LOG((LF_GC, LL_INFO1000, " Blocks Scanned = %u (%u slots)\n", info->DEBUG_BlocksScanned, blockHandles));
1227
1228 // if we scanned any blocks then summarize some stats
1229 if (blockHandles)
1230 {
1231 uint32_t nonTrivialBlockHandles = info->DEBUG_BlocksScannedNonTrivially * HANDLE_HANDLES_PER_BLOCK;
1232 LOG((LF_GC, LL_INFO1000, " Blocks Examined = %u (%u slots)\n", info->DEBUG_BlocksScannedNonTrivially, nonTrivialBlockHandles));
1233
1234 LOG((LF_GC, LL_INFO1000, " Slots Scanned = %u\n", info->DEBUG_HandleSlotsScanned));
1235 LOG((LF_GC, LL_INFO1000, " Handles Scanned = %u\n", info->DEBUG_HandlesActuallyScanned));
1236
1237 double scanRatio = ((double)info->DEBUG_HandlesActuallyScanned / (double)blockHandles) * 100.0;
1238
1239 LOG((LF_GC, LL_INFO1000, " Handle Scanning Ratio = %1.1lf%%\n", scanRatio));
1240 }
1241
1242 // dump a footer for the stats
1243 LOG((LF_GC, LL_INFO1000, "--------------------------------------------------------------\n"));
1244 }
1245}
1246
1247void DEBUG_LogScanningStatistics(HandleTable *pTable, uint32_t level)
1248{
1249 WRAPPER_NO_CONTRACT;
1250 UNREFERENCED_PARAMETER(level);
1251
1252 // have we done any GC's yet?
1253 if (pTable->_DEBUG_iMaxGen >= 0)
1254 {
1255 // dump a header for the stats
1256 LOG((LF_GC, level, "\n==============================================================\n"));
1257 LOG((LF_GC, level, " Cumulative Handle Scan Summary:\n"));
1258
1259 // for each generation we've collected, dump the current stats
1260 for (int i = 0; i <= pTable->_DEBUG_iMaxGen; i++)
1261 {
1262 int64_t totalBlocksScanned = pTable->_DEBUG_TotalBlocksScanned[i];
1263
1264 // dump the generation number and the number of blocks scanned
1265 LOG((LF_GC, level, "--------------------------------------------------------------\n"));
1266 LOG((LF_GC, level, " Condemned Generation = %d\n", i));
1267 LOG((LF_GC, level, " Blocks Scanned = %I64u\n", totalBlocksScanned));
1268
1269 // if we scanned any blocks in this generation then dump some interesting numbers
1270 if (totalBlocksScanned)
1271 {
1272 LOG((LF_GC, level, " Blocks Examined = %I64u\n", pTable->_DEBUG_TotalBlocksScannedNonTrivially[i]));
1273 LOG((LF_GC, level, " Slots Scanned = %I64u\n", pTable->_DEBUG_TotalHandleSlotsScanned [i]));
1274 LOG((LF_GC, level, " Handles Scanned = %I64u\n", pTable->_DEBUG_TotalHandlesActuallyScanned [i]));
1275
1276 double blocksScanned = (double) totalBlocksScanned;
1277 double blocksExamined = (double) pTable->_DEBUG_TotalBlocksScannedNonTrivially[i];
1278 double slotsScanned = (double) pTable->_DEBUG_TotalHandleSlotsScanned [i];
1279 double handlesScanned = (double) pTable->_DEBUG_TotalHandlesActuallyScanned [i];
1280 double totalSlots = (double) (totalBlocksScanned * HANDLE_HANDLES_PER_BLOCK);
1281
1282 LOG((LF_GC, level, " Block Scan Ratio = %1.1lf%%\n", (100.0 * (blocksExamined / blocksScanned)) ));
1283 LOG((LF_GC, level, " Clump Scan Ratio = %1.1lf%%\n", (100.0 * (slotsScanned / totalSlots)) ));
1284 LOG((LF_GC, level, " Scanned Clump Saturation = %1.1lf%%\n", (100.0 * (handlesScanned / slotsScanned)) ));
1285 LOG((LF_GC, level, " Overall Handle Scan Ratio = %1.1lf%%\n", (100.0 * (handlesScanned / totalSlots)) ));
1286 }
1287 }
1288
1289 // dump a footer for the stats
1290 LOG((LF_GC, level, "==============================================================\n\n"));
1291 }
1292}
1293
1294#endif // _DEBUG
1295#endif // !DACCESS_COMPILE
1296
1297
1298/*--------------------------------------------------------------------------*/
1299
1300
1301