Skip to content

Commit fdce40c

Browse files
Merge branch 'main' into copilot/fix-com-interop-marshaling
2 parents 16ad0c4 + db4b5d3 commit fdce40c

43 files changed

Lines changed: 1318 additions & 404 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/design/datacontracts/GC.md

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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
291317
GCHeapType 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+
```

docs/design/datacontracts/RuntimeTypeSystem.md

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,15 @@ public enum OptimizationTier
157157
}
158158
```
159159

160+
```csharp
161+
public enum GenericContextLoc
162+
{
163+
None,
164+
InstArg,
165+
ThisPtr,
166+
}
167+
```
168+
160169
```csharp
161170
partial interface IRuntimeTypeSystem : IContract
162171
{
@@ -199,9 +208,9 @@ partial interface IRuntimeTypeSystem : IContract
199208
// Return true if a MethodDesc represents an IL stub with a special MethodDesc context arg
200209
public virtual bool HasMDContextArg(MethodDescHandle);
201210

202-
// Return true if the method requires a hidden instantiation argument (generic context parameter).
203-
// Corresponds to native MethodDesc::RequiresInstArg().
204-
public virtual bool RequiresInstArg(MethodDescHandle methodDesc);
211+
// Determine where a shared generic method obtains its generic context.
212+
// Returns None if not shared, InstArg if via a hidden parameter, or ThisPtr if via the 'this' pointer's MethodTable.
213+
public virtual GenericContextLoc GetGenericContextLoc(MethodDescHandle methodDesc);
205214

206215
// Return true if the method uses the async calling convention.
207216
// Corresponds to native MethodDesc::IsAsyncMethod().
@@ -1637,26 +1646,36 @@ Determining if a method is an async thunk method:
16371646
}
16381647
```
16391648

1640-
Determining if a method requires a hidden instantiation argument (generic context parameter):
1649+
Determining where a shared generic method obtains its generic context:
16411650

16421651
```csharp
1643-
public bool RequiresInstArg(MethodDescHandle methodDescHandle)
1652+
public GenericContextLoc GetGenericContextLoc(MethodDescHandle methodDescHandle)
16441653
{
16451654
MethodDesc md = _methodDescs[methodDescHandle.Address];
1646-
1647-
// RequiresInstArg = IsSharedByGenericInstantiations && (HasMethodInstantiation || IsStatic || IsValueType || IsInterface)
16481655
if (!IsSharedByGenericInstantiations(md))
1649-
return false;
1656+
return GenericContextLoc.None;
1657+
else if (RequiresInstArg(md))
1658+
return GenericContextLoc.InstArg;
1659+
else
1660+
return GenericContextLoc.ThisPtr;
1661+
}
16501662

1663+
private bool RequiresInstArg(MethodDesc md)
1664+
{
16511665
if (HasMethodInstantiation(md))
16521666
return true;
16531667

1654-
// md.IsStatic reads from MethodDescFlags.Static (0x0080)
16551668
if (md.IsStatic)
16561669
return true;
16571670

16581671
MethodTable mt = _methodTables[md.MethodTable];
1659-
return mt.Flags.IsInterface || mt.Flags.IsValueType;
1672+
if (mt.Flags.IsValueType)
1673+
return true;
1674+
1675+
if (mt.Flags.IsInterface && !IsAbstract(md) /* checked from md classification and metadata attributes */)
1676+
return true;
1677+
1678+
return false;
16601679
}
16611680

16621681
private bool IsSharedByGenericInstantiations(MethodDesc md)

src/coreclr/debug/daccess/daccess.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7976,7 +7976,7 @@ void DacFreeRegionEnumerator::AddSingleSegment(const dac_heap_segment &curr, Fre
79767976

79777977
void DacFreeRegionEnumerator::AddSegmentList(DPTR(dac_heap_segment) start, FreeRegionKind kind, int heap)
79787978
{
7979-
int iterationMax = 2048;
7979+
int iterationMax = 65536;
79807980

79817981
DPTR(dac_heap_segment) curr = start;
79827982
while (curr != nullptr)

src/coreclr/debug/daccess/dacdbiimpl.cpp

Lines changed: 23 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -6875,10 +6875,14 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::WalkHeap(HeapWalkHandle handle,
68756875

68766876

68776877

6878-
HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetHeapSegments(OUT DacDbiArrayList<COR_SEGMENT> *pSegments)
6878+
HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::EnumerateHeapSegments(
6879+
FP_HEAPSEGMENT_CALLBACK fpCallback,
6880+
CALLBACK_DATA pUserData)
68796881
{
68806882
DD_ENTER_MAY_THROW;
68816883

6884+
if (fpCallback == NULL)
6885+
return E_POINTER;
68826886

68836887
size_t heapCount = 0;
68846888
HeapData *heaps = 0;
@@ -6893,53 +6897,28 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetHeapSegments(OUT DacDbiArrayLi
68936897

68946898
NewArrayHolder<HeapData> _heapHolder = heaps;
68956899

6896-
// Count the number of segments to know how much to allocate.
6897-
int total = 0;
6898-
for (size_t i = 0; i < heapCount; ++i)
6899-
{
6900-
total += (int)heaps[i].SegmentCount;
6901-
if (!region)
6902-
{
6903-
// SegmentCount is +1 due to the ephemeral segment containing more than one
6904-
// generation (Gen1 + Gen0, and sometimes part of Gen2).
6905-
total++;
6906-
6907-
// It's possible that part of Gen2 lives on the ephemeral segment. If so,
6908-
// we need to add one more to the output.
6909-
const size_t eph = heaps[i].EphemeralSegment;
6910-
_ASSERTE(eph < heaps[i].SegmentCount);
6911-
if (heaps[i].Segments[eph].Start != heaps[i].Gen1Start)
6912-
total++;
6913-
}
6914-
}
6915-
6916-
pSegments->Alloc(total);
6900+
if (FAILED(hr))
6901+
return hr;
69176902

6918-
// Now walk all segments and write them to the array.
6919-
int curr = 0;
6903+
// Now walk all segments and emit them through the callback.
69206904
for (size_t i = 0; i < heapCount; ++i)
69216905
{
6922-
_ASSERTE(curr < total);
69236906
if (!region)
69246907
{
69256908
// Generation 0 is not in the segment list.
6926-
COR_SEGMENT &seg = (*pSegments)[curr++];
6927-
seg.start = heaps[i].Gen0Start;
6928-
seg.end = heaps[i].Gen0End;
6929-
seg.type = CorDebug_Gen0;
6930-
seg.heap = (ULONG)i;
6909+
fpCallback(heaps[i].Gen0Start, heaps[i].Gen0End, (int)CorDebug_Gen0, (ULONG)i, pUserData);
69316910
}
69326911

69336912
for (size_t j = 0; j < heaps[i].SegmentCount; ++j)
69346913
{
69356914
if (region)
69366915
{
6937-
_ASSERTE(curr < total);
6938-
COR_SEGMENT &seg = (*pSegments)[curr++];
6939-
seg.start = heaps[i].Segments[j].Start;
6940-
seg.end = heaps[i].Segments[j].End;
6941-
seg.type = (CorDebugGenerationTypes)heaps[i].Segments[j].Generation;
6942-
seg.heap = (ULONG)i;
6916+
fpCallback(
6917+
heaps[i].Segments[j].Start,
6918+
heaps[i].Segments[j].End,
6919+
(int)heaps[i].Segments[j].Generation,
6920+
(ULONG)i,
6921+
pUserData);
69436922
}
69446923
else if (heaps[i].Segments[j].Generation == 1)
69456924
{
@@ -6948,43 +6927,29 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetHeapSegments(OUT DacDbiArrayLi
69486927
_ASSERTE(heaps[i].Segments[j].Start <= heaps[i].Gen1Start);
69496928
_ASSERTE(heaps[i].Segments[j].End > heaps[i].Gen1Start);
69506929

6951-
{
6952-
_ASSERTE(curr < total);
6953-
COR_SEGMENT &seg = (*pSegments)[curr++];
6954-
seg.start = heaps[i].Gen1Start;
6955-
seg.end = heaps[i].Gen0Start;
6956-
seg.type = CorDebug_Gen1;
6957-
seg.heap = (ULONG)i;
6958-
}
6930+
fpCallback(heaps[i].Gen1Start, heaps[i].Gen0Start, (int)CorDebug_Gen1, (ULONG)i, pUserData);
69596931

69606932
// It's possible for Gen2 to take up a portion of the ephemeral segment.
69616933
// We test for that here.
69626934
if (heaps[i].Segments[j].Start != heaps[i].Gen1Start)
69636935
{
6964-
_ASSERTE(curr < total);
6965-
COR_SEGMENT &seg = (*pSegments)[curr++];
6966-
seg.start = heaps[i].Segments[j].Start;
6967-
seg.end = heaps[i].Gen1Start;
6968-
seg.type = CorDebug_Gen2;
6969-
seg.heap = (ULONG)i;
6936+
fpCallback(heaps[i].Segments[j].Start, heaps[i].Gen1Start, (int)CorDebug_Gen2, (ULONG)i, pUserData);
69706937
}
69716938
}
69726939
else
69736940
{
69746941
// Otherwise, we have a gen2, POH, LOH or NonGC
6975-
_ASSERTE(curr < total);
6976-
COR_SEGMENT &seg = (*pSegments)[curr++];
6977-
seg.start = heaps[i].Segments[j].Start;
6978-
seg.end = heaps[i].Segments[j].End;
6979-
69806942
_ASSERTE(heaps[i].Segments[j].Generation <= CorDebug_NonGC);
6981-
seg.type = (CorDebugGenerationTypes)heaps[i].Segments[j].Generation;
6982-
seg.heap = (ULONG)i;
6943+
fpCallback(
6944+
heaps[i].Segments[j].Start,
6945+
heaps[i].Segments[j].End,
6946+
(int)heaps[i].Segments[j].Generation,
6947+
(ULONG)i,
6948+
pUserData);
69836949
}
69846950
}
69856951
}
69866952

6987-
_ASSERTE(total == curr);
69886953
return hr;
69896954
}
69906955

src/coreclr/debug/daccess/dacdbiimpl.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ class DacDbiInterfaceImpl :
118118
OUT COR_HEAPOBJECT * objects,
119119
OUT ULONG *fetched);
120120

121-
HRESULT STDMETHODCALLTYPE GetHeapSegments(OUT DacDbiArrayList<COR_SEGMENT> *pSegments);
121+
HRESULT STDMETHODCALLTYPE EnumerateHeapSegments(FP_HEAPSEGMENT_CALLBACK fpCallback, CALLBACK_DATA pUserData);
122122

123123

124124
HRESULT STDMETHODCALLTYPE IsValidObject(CORDB_ADDRESS obj, OUT BOOL * pResult);

0 commit comments

Comments
 (0)