@@ -125,6 +125,11 @@ public readonly struct GCOomData
125125 IReadOnlyList < GCMemoryRegionData > GetGCBookkeepingMemoryRegions ();
126126 // Gets GC free regions (free region lists and freeable segments)
127127 IReadOnlyList < GCMemoryRegionData > GetGCFreeRegions ();
128+
129+ // Enumerates every GC heap segment for the supplied heap data. Each yielded GCHeapSegmentInfo
130+ // describes a single segment with the inclusive start and exclusive end of its memory range
131+ // and its generation tag (or Ephemeral).
132+ IEnumerable < GCHeapSegmentInfo > EnumerateHeapSegments (GCHeapData heapData );
128133```
129134
130135``` csharp
@@ -145,6 +150,26 @@ public readonly struct GCMemoryRegionData
145150 public ulong ExtraData { get ; init ; }
146151 public int Heap { get ; init ; }
147152}
153+
154+ public enum GCSegmentClassification
155+ {
156+ Unknown ,
157+ Gen0 ,
158+ Gen1 ,
159+ Gen2 ,
160+ LOH ,
161+ POH ,
162+ NonGC ,
163+ // Segments-GC only: marker used by IGC.EnumerateHeapSegments to denote the ephemeral
164+ // segment on the gen2 list. The caller is responsible for splitting it into the Gen1
165+ // piece and an optional Gen2 prefix.
166+ Ephemeral ,
167+ }
168+
169+ public readonly record struct GCHeapSegmentInfo (
170+ TargetPointer Start ,
171+ TargetPointer End ,
172+ GCSegmentClassification Generation );
148173```
149174
150175## Version 1
@@ -286,6 +311,7 @@ Constants used:
286311| Name | Type | Purpose | Value |
287312| --- | --- | --- | --- |
288313| ` WRK_HEAP_COUNT ` | uint | The number of heaps in the ` workstation ` GC type | ` 1 ` |
314+ | ` HEAP_SEGMENT_FLAGS_READONLY ` | ulong | ` HeapSegment.Flags ` bit identifying a readonly (e.g. frozen, non-GC) segment. | ` 1 ` |
289315
290316``` csharp
291317GCHeapType IGC .GetGCIdentifiers ()
@@ -858,7 +884,7 @@ IReadOnlyList<GCMemoryRegionData> IGC.GetHandleTableMemoryRegions()
858884 // Safety caps matching native DAC
859885 const int MaxHandleTableRegions = 8192 ;
860886 const int MaxBookkeepingRegions = 32 ;
861- const int MaxSegmentListIterations = 2048 ;
887+ const int MaxSegmentListIterations = 65536 ;
862888
863889 int maxRegions = MaxHandleTableRegions ;
864890 TargetPointer handleTableMap = target .ReadGlobalPointer (" HandleTableMap" );
@@ -1027,3 +1053,79 @@ TargetNUInt IGC.GetHandleExtraInfo(TargetPointer handle)
10271053 return target .ReadNUInt (extraInfoAddr );
10281054}
10291055```
1056+
1057+ EnumerateHeapSegments
1058+
1059+ Returns the raw GC heap segments for a single heap by walking the per-generation segment
1060+ lists.
1061+ ``` csharp
1062+ IEnumerable < GCHeapSegmentInfo > IGC .EnumerateHeapSegments (GCHeapData heapData )
1063+ {
1064+ // The generation table is laid out as gen0, gen1, gen2, LOH, POH (plus optional extras).
1065+ var gens = heapData .GenerationTable ;
1066+ bool regions = GetGCIdentifiers ().Contains (" regions" );
1067+
1068+ TargetPointer ephemeralSegment = heapData .EphemeralHeapSegment ;
1069+ TargetPointer allocAllocated = heapData .AllocAllocated ;
1070+
1071+ if (regions )
1072+ {
1073+ // In regions mode each generation has its own segment list. Readonly entries on
1074+ // the gen2 list represent non-GC (e.g. frozen) regions and are reported as NonGC.
1075+ foreach (var (seg , _ ) in WalkSegmentList (gens [2 ].StartSegment ))
1076+ {
1077+ var type = (seg .Flags & HEAP_SEGMENT_FLAGS_READONLY ) != 0
1078+ ? GCSegmentClassification .NonGC
1079+ : GCSegmentClassification .Gen2 ;
1080+ yield return new GCHeapSegmentInfo (seg .Mem , seg .Allocated , type );
1081+ }
1082+ foreach (var (seg , _ ) in WalkSegmentList (gens [1 ].StartSegment ))
1083+ yield return new GCHeapSegmentInfo (seg .Mem , seg .Allocated , GCSegmentClassification .Gen1 );
1084+ foreach (var (seg , segAddr ) in WalkSegmentList (gens [0 ].StartSegment ))
1085+ {
1086+ // For the gen0 segment that matches the ephemeral_heap_segment, end is alloc_allocated.
1087+ TargetPointer end = segAddr == ephemeralSegment ? allocAllocated : seg .Allocated ;
1088+ yield return new GCHeapSegmentInfo (seg .Mem , end , GCSegmentClassification .Gen0 );
1089+ }
1090+ }
1091+ else
1092+ {
1093+ // In segments mode the gen2 list contains every SOH segment. The ephemeral
1094+ // segment is tagged Ephemeral as the layer-2 split marker; non-ephemeral entries
1095+ // are reported with their true generation (Gen2 or NonGC for readonly).
1096+ foreach (var (seg , segAddr ) in WalkSegmentList (gens [2 ].StartSegment ))
1097+ {
1098+ GCSegmentClassification type ;
1099+ if (segAddr == ephemeralSegment )
1100+ type = GCSegmentClassification .Ephemeral ;
1101+ else if ((seg .Flags & HEAP_SEGMENT_FLAGS_READONLY ) != 0 )
1102+ type = GCSegmentClassification .NonGC ;
1103+ else
1104+ type = GCSegmentClassification .Gen2 ;
1105+ TargetPointer end = segAddr == ephemeralSegment ? allocAllocated : seg .Allocated ;
1106+ yield return new GCHeapSegmentInfo (seg .Mem , end , type );
1107+ }
1108+ }
1109+
1110+ // LOH and POH segments are always reported as-is regardless of GC mode.
1111+ foreach (var (seg , _ ) in WalkSegmentList (gens [3 ].StartSegment ))
1112+ yield return new GCHeapSegmentInfo (seg .Mem , seg .Allocated , GCSegmentClassification .LOH );
1113+ foreach (var (seg , _ ) in WalkSegmentList (gens [4 ].StartSegment ))
1114+ yield return new GCHeapSegmentInfo (seg .Mem , seg .Allocated , GCSegmentClassification .POH );
1115+ }
1116+
1117+ IEnumerable < (HeapSegment Segment , TargetPointer Address )> WalkSegmentList (TargetPointer startSegment )
1118+ {
1119+ // Bounded traversal of the singly-linked HeapSegment list, guarding against cycles or
1120+ // corrupt links via a fixed iteration cap (MaxSegmentListIterations = 65536).
1121+ int iterationMax = MaxSegmentListIterations ;
1122+ TargetPointer current = startSegment ;
1123+ while (current != TargetPointer .Null )
1124+ {
1125+ HeapSegment seg = /* read HeapSegment at current */ ;
1126+ yield return (seg , current );
1127+ current = seg .Next ;
1128+ if (iterationMax -- <= 0 ) throw /* cycle detected */ ;
1129+ }
1130+ }
1131+ ```
0 commit comments