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) |
25 | DWORD g_dwHandles = 0; |
26 | #endif // ENABLE_PERF_COUNTERS || FEATURE_EVENT_TRACE |
27 | |
28 | /**************************************************************************** |
29 | * |
30 | * FORWARD DECLARATIONS |
31 | * |
32 | ****************************************************************************/ |
33 | |
34 | #ifdef _DEBUG |
35 | void DEBUG_PostGCScanHandler(HandleTable *pTable, const uint32_t *types, uint32_t typeCount, uint32_t condemned, uint32_t maxgen, ScanCallbackInfo *info); |
36 | void 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 | */ |
86 | HHANDLETABLE 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 | */ |
180 | void 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 | */ |
220 | void 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 | */ |
236 | uint32_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 | */ |
252 | ADIndex 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 | */ |
267 | GC_DAC_VISIBLE |
268 | ADIndex 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 | */ |
286 | OBJECTHANDLE HndCreateHandle(HHANDLETABLE hTable, uint32_t uType, OBJECTREF object, uintptr_t ) |
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 |
355 | void 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 | |
373 | void 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 | |
391 | void 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 | */ |
428 | void 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 | */ |
475 | void 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 | */ |
506 | void HndSetHandleExtraInfo(OBJECTHANDLE handle, uint32_t uType, uintptr_t ) |
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 | */ |
527 | uintptr_t HndCompareExchangeHandleExtraInfo(OBJECTHANDLE handle, uint32_t uType, uintptr_t , uintptr_t ) |
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 | */ |
552 | GC_DAC_VISIBLE |
553 | uintptr_t HndGetHandleExtraInfo(OBJECTHANDLE handle) |
554 | { |
555 | WRAPPER_NO_CONTRACT; |
556 | |
557 | // assume zero until we actually get it |
558 | uintptr_t = 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 | */ |
579 | HHANDLETABLE 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 | |
589 | void 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 | */ |
645 | void 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 | */ |
722 | GC_DAC_VISIBLE_NO_MANGLE |
723 | void 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 | */ |
783 | GC_DAC_VISIBLE_NO_MANGLE |
784 | void 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 | */ |
912 | void 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 | */ |
952 | void 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 | */ |
986 | void 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 | |
1010 | extern 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 | */ |
1024 | uint32_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 | */ |
1085 | uint32_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 | |
1138 | BOOL 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 | |
1162 | void 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 | |
1192 | void 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 | |
1247 | void 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 | |