2929
3030#include " gcdesc.h"
3131
32+ #ifdef FEATURE_EVENT_TRACE
33+ #include " clretwallmain.h"
34+ #else // FEATURE_EVENT_TRACE
35+ #include " etmdummy.h"
36+ #endif // FEATURE_EVENT_TRACE
37+
3238#define RH_LARGE_OBJECT_SIZE 85000
3339
3440MethodTable g_FreeObjectEEType;
@@ -471,6 +477,29 @@ EXTERN_C int64_t QCALLTYPE RhGetTotalAllocatedBytesPrecise()
471477 return allocated;
472478}
473479
480+ void FireAllocationSampled (GC_ALLOC_FLAGS flags, size_t size, size_t samplingBudgetOffset, Object* orObject)
481+ {
482+ #ifdef FEATURE_EVENT_TRACE
483+ void * typeId = GetLastAllocEEType ();
484+ // Note: Just as for AllocationTick, the type name cannot be retrieved
485+ WCHAR* name = nullptr ;
486+
487+ if (typeId != nullptr )
488+ {
489+ unsigned int allocKind =
490+ (flags & GC_ALLOC_PINNED_OBJECT_HEAP) ? 2 :
491+ (flags & GC_ALLOC_LARGE_OBJECT_HEAP) ? 1 :
492+ 0 ; // SOH
493+ unsigned int heapIndex = 0 ;
494+ #ifdef BACKGROUND_GC
495+ gc_heap* hp = gc_heap::heap_of ((BYTE*)orObject);
496+ heapIndex = hp->heap_number ;
497+ #endif
498+ FireEtwAllocationSampled (allocKind, GetClrInstanceId (), typeId, name, heapIndex, (BYTE*)orObject, size, samplingBudgetOffset);
499+ }
500+ #endif
501+ }
502+
474503static Object* GcAllocInternal (MethodTable* pEEType, uint32_t uFlags, uintptr_t numElements, Thread* pThread)
475504{
476505 ASSERT (!pThread->IsDoNotTriggerGcSet ());
@@ -539,8 +568,47 @@ static Object* GcAllocInternal(MethodTable* pEEType, uint32_t uFlags, uintptr_t
539568 // Save the MethodTable for instrumentation purposes.
540569 tls_pLastAllocationEEType = pEEType;
541570
542- Object* pObject = GCHeapUtilities::GetGCHeap ()->Alloc (pThread->GetAllocContext (), cbSize, uFlags);
543- pThread->GetEEAllocContext ()->UpdateCombinedLimit ();
571+ // check for dynamic allocation sampling
572+ ee_alloc_context* pEEAllocContext = pThread->GetEEAllocContext ();
573+ gc_alloc_context* pAllocContext = pEEAllocContext->GetGCAllocContext ();
574+ bool isSampled = false ;
575+ size_t availableSpace = 0 ;
576+ size_t samplingBudget = 0 ;
577+
578+ bool isRandomizedSamplingEnabled = ee_alloc_context::IsRandomizedSamplingEnabled ();
579+ if (isRandomizedSamplingEnabled)
580+ {
581+ // The number bytes we can allocate before we need to emit a sampling event.
582+ // This calculation is only valid if combined_limit < alloc_limit.
583+ samplingBudget = (size_t )(pEEAllocContext->combined_limit - pAllocContext->alloc_ptr );
584+
585+ // The number of bytes available in the current allocation context
586+ availableSpace = (size_t )(pAllocContext->alloc_limit - pAllocContext->alloc_ptr );
587+
588+ // Check to see if the allocated object overlaps a sampled byte
589+ // in this AC. This happens when both:
590+ // 1) The AC contains a sampled byte (combined_limit < alloc_limit)
591+ // 2) The object is large enough to overlap it (samplingBudget < aligned_size)
592+ //
593+ // Note that the AC could have no remaining space for allocations (alloc_ptr =
594+ // alloc_limit = combined_limit). When a thread hasn't done any SOH allocations
595+ // yet it also starts in an empty state where alloc_ptr = alloc_limit =
596+ // combined_limit = nullptr. The (1) check handles both of these situations
597+ // properly as an empty AC can not have a sampled byte inside of it.
598+ isSampled =
599+ (pEEAllocContext->combined_limit < pAllocContext->alloc_limit ) &&
600+ (samplingBudget < cbSize);
601+
602+ // if the object overflows the AC, we need to sample the remaining bytes
603+ // the sampling budget only included at most the bytes inside the AC
604+ if (cbSize > availableSpace && !isSampled)
605+ {
606+ samplingBudget = ee_alloc_context::ComputeGeometricRandom () + availableSpace;
607+ isSampled = (samplingBudget < cbSize);
608+ }
609+ }
610+
611+ Object* pObject = GCHeapUtilities::GetGCHeap ()->Alloc (pAllocContext, cbSize, uFlags);
544612 if (pObject == NULL )
545613 return NULL ;
546614
@@ -551,6 +619,19 @@ static Object* GcAllocInternal(MethodTable* pEEType, uint32_t uFlags, uintptr_t
551619 ((Array*)pObject)->InitArrayLength ((uint32_t )numElements);
552620 }
553621
622+ if (isSampled)
623+ {
624+ FireAllocationSampled ((GC_ALLOC_FLAGS)uFlags, cbSize, samplingBudget, pObject);
625+ }
626+
627+ // There are a variety of conditions that may have invalidated the previous combined_limit value
628+ // such as not allocating the object in the AC memory region (UOH allocations), moving the AC, adding
629+ // extra alignment padding, allocating a new AC, or allocating an object that consumed the sampling budget.
630+ // Rather than test for all the different invalidation conditions individually we conservatively always
631+ // recompute it. If sampling isn't enabled this inlined function is just trivially setting
632+ // combined_limit=alloc_limit.
633+ pEEAllocContext->UpdateCombinedLimit (isRandomizedSamplingEnabled);
634+
554635 if (uFlags & GC_ALLOC_USER_OLD_HEAP)
555636 GCHeapUtilities::GetGCHeap ()->PublishObject ((uint8_t *)pObject);
556637
0 commit comments