Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions docs/design/datacontracts/RuntimeTypeSystem.md
Original file line number Diff line number Diff line change
Expand Up @@ -311,9 +311,10 @@ uint GetFieldDescMemberDef(TargetPointer fieldDescPointer);
bool IsFieldDescThreadStatic(TargetPointer fieldDescPointer);
bool IsFieldDescStatic(TargetPointer fieldDescPointer);
bool IsFieldDescRVA(TargetPointer fieldDescPointer);
uint GetFieldDescType(TargetPointer fieldDescPointer);
CorElementType GetFieldDescType(TargetPointer fieldDescPointer);
uint GetFieldDescOffset(TargetPointer fieldDescPointer, FieldDefinition? fieldDef);
TypeHandle GetFieldDescApproxTypeHandle(TargetPointer fieldDescPointer);
bool TryGetFieldDescNext(TargetPointer fieldDescPointer, out TargetPointer nextFieldDesc);
TargetPointer GetFieldDescStaticAddress(TargetPointer fieldDescPointer, bool unboxValueTypes = true);
TargetPointer GetFieldDescThreadStaticAddress(TargetPointer fieldDescPointer, TargetPointer thread, bool unboxValueTypes = true);
```
Expand Down Expand Up @@ -2273,10 +2274,10 @@ bool IsFieldDescRVA(TargetPointer fieldDescPointer)
return (DWord1 & (uint)FieldDescFlags1.IsRVA) != 0;
}

uint GetFieldDescType(TargetPointer fieldDescPointer)
CorElementType GetFieldDescType(TargetPointer fieldDescPointer)
{
uint DWord2 = target.Read<uint>(fieldDescPointer + /* FieldDesc::DWord2 offset */);
return (DWord2 & (uint)FieldDescFlags2.TypeMask) >> 27;
return (CorElementType)((DWord2 & (uint)FieldDescFlags2.TypeMask) >> 27);
}

uint GetFieldDescOffset(TargetPointer fieldDescPointer, FieldDefinition? fieldDef)
Expand Down Expand Up @@ -2319,6 +2320,26 @@ TypeHandle GetFieldDescApproxTypeHandle(TargetPointer fieldDescPointer)
// TypeHandle. Returns TypeHandle.Null if any link in the chain is unavailable
// (e.g. uncached constructed instantiation).
}

bool TryGetFieldDescNext(TargetPointer fieldDescPointer, out TargetPointer nextFieldDesc)
{
// The FieldDescs of a type form a contiguous array with no terminator. Advance one FieldDesc-size
// along, but first bounds-check: locate the enclosing type (via the FieldDesc's enclosing
// MethodTable) and, if `fieldDescPointer` is the last FieldDesc in that type's list, report that
// there is no next FieldDesc by returning false.
TargetPointer enclosingMT = GetMTOfEnclosingClass(fieldDescPointer);
TypeHandle typeHandle = GetTypeHandle(enclosingMT);
// The field list holds the type's own instance fields (total instance fields minus the parent's)
// followed by its static fields; see GetFieldDescList.
TargetPointer lastFieldDesc = /* address of the final FieldDesc in typeHandle's list */;
if (fieldDescPointer == lastFieldDesc)
{
nextFieldDesc = TargetPointer.Null;
return false;
}
nextFieldDesc = fieldDescPointer + /* sizeof(FieldDesc) */;
return true;
}
```

### Other APIs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@ bool TryGetHFAElementSize(TypeHandle typeHandle, out int elementSize)
CorElementType GetFieldDescType(TargetPointer fieldDescPointer) => throw new NotImplementedException();
uint GetFieldDescOffset(TargetPointer fieldDescPointer, FieldDefinition? fieldDef) => throw new NotImplementedException();
TypeHandle GetFieldDescApproxTypeHandle(TargetPointer fieldDescPointer) => throw new NotImplementedException();
bool TryGetFieldDescNext(TargetPointer fieldDescPointer, out TargetPointer nextFieldDesc) => throw new NotImplementedException();
TargetPointer GetFieldDescByName(TypeHandle typeHandle, string fieldName) => throw new NotImplementedException();
TargetPointer GetFieldDescStaticAddress(TargetPointer fieldDescPointer, bool unboxValueTypes = true) => throw new NotImplementedException();
TargetPointer GetFieldDescThreadStaticAddress(TargetPointer fieldDescPointer, TargetPointer thread, bool unboxValueTypes = true) => throw new NotImplementedException();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -872,6 +872,17 @@ public IEnumerable<TargetPointer> GetFieldDescList(TypeHandle typeHandle)
if (!typeHandle.IsMethodTable())
yield break;

(TargetPointer fieldDescListPtr, uint fieldDescSize, int totalFields) = GetFieldDescListLayout(typeHandle);
for (int i = 0; i < totalFields; i++)
{
yield return fieldDescListPtr + (ulong)i * fieldDescSize;
}
}

// Returns the start pointer, per-element size, and count of the enclosing type's contiguous FieldDesc
// array (the fields declared by the type: its own instance fields plus its static fields).
private (TargetPointer ListStart, uint FieldDescSize, int TotalFields) GetFieldDescListLayout(TypeHandle typeHandle)
{
TargetPointer fieldDescListPtr = GetClassData(typeHandle).FieldDescList;
uint fieldDescSize = _target.GetTypeInfo(DataType.FieldDesc).Size!.Value;

Expand All @@ -883,10 +894,7 @@ public IEnumerable<TargetPointer> GetFieldDescList(TypeHandle typeHandle)
numInstanceFields -= GetNumInstanceFields(parentHandle);
}
int totalFields = numInstanceFields + GetNumStaticFields(typeHandle);
for (int i = 0; i < totalFields; i++)
{
yield return fieldDescListPtr + (ulong)i * fieldDescSize;
}
return (fieldDescListPtr, fieldDescSize, totalFields);
}
public bool IsTrackedReferenceWithFinalizer(TypeHandle typeHandle) => typeHandle.IsMethodTable() && _methodTables[typeHandle.Address].Flags.IsTrackedReferenceWithFinalizer;
private TargetPointer GetDynamicStaticsInfo(TypeHandle typeHandle)
Expand Down Expand Up @@ -2329,6 +2337,24 @@ TypeHandle IRuntimeTypeSystem.GetFieldDescApproxTypeHandle(TargetPointer fieldDe
}
}

bool IRuntimeTypeSystem.TryGetFieldDescNext(TargetPointer fieldDescPointer, out TargetPointer nextFieldDesc)
{
// Bounds check the advance: the FieldDescs of a type form a contiguous array with no terminator,
// so advancing past the last one would yield a pointer that is not a valid FieldDesc. Locate the
// enclosing type and return false when this is the final FieldDesc in its list.
TargetPointer enclosingMT = ((IRuntimeTypeSystem)this).GetMTOfEnclosingClass(fieldDescPointer);
(TargetPointer listStart, uint fieldDescSize, int totalFields) = GetFieldDescListLayout(GetTypeHandle(enclosingMT));

TargetPointer lastFieldDesc = listStart + (ulong)(totalFields - 1) * fieldDescSize;
if (fieldDescPointer == lastFieldDesc)
{
nextFieldDesc = TargetPointer.Null;
return false;
}
nextFieldDesc = fieldDescPointer + fieldDescSize;
return true;
}
Comment thread
max-charlamb marked this conversation as resolved.

TargetPointer IRuntimeTypeSystem.GetFieldDescByName(TypeHandle typeHandle, string fieldName)
{
if (!typeHandle.IsMethodTable())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1057,7 +1057,6 @@ int ISOSDacInterface.GetFieldDescData(ClrDataAddress fieldDesc, DacpFieldDescDat

IRuntimeTypeSystem rtsContract = _target.Contracts.RuntimeTypeSystem;
IEcmaMetadata ecmaMetadataContract = _target.Contracts.EcmaMetadata;
ISignature signatureContract = _target.Contracts.Signature;

TargetPointer fieldDescTargetPtr = fieldDesc.ToTargetPointer(_target);
CorElementType fieldDescType = rtsContract.GetFieldDescType(fieldDescTargetPtr);
Expand All @@ -1073,17 +1072,19 @@ int ISOSDacInterface.GetFieldDescData(ClrDataAddress fieldDesc, DacpFieldDescDat
Contracts.ModuleHandle moduleHandle = _target.Contracts.Loader.GetModuleHandleFromModulePtr(modulePtr);
MetadataReader mdReader = ecmaMetadataContract.GetMetadata(moduleHandle)!;
FieldDefinition fieldDef = mdReader.GetFieldDefinition(fieldHandle);

TypeHandle foundTypeHandle = rtsContract.GetFieldDescApproxTypeHandle(fieldDescTargetPtr);
try
{
// try to completely decode the signature
TypeHandle foundTypeHandle = signatureContract.DecodeFieldSignature(fieldDef.Signature, moduleHandle, ctx);

// get the MT of the type
// This is an implementation detail of the DAC that we replicate here to get method tables for non-MT types
// that we can return to SOS for pretty-printing.
// In the future we may want to return a TypeHandle instead of a MethodTable, and modify SOS to do more complete pretty-printing.
// DAC equivalent: src/coreclr/vm/typehandle.inl TypeHandle::GetMethodTable
if (rtsContract.IsFunctionPointer(foundTypeHandle, out _, out _) || rtsContract.IsPointer(foundTypeHandle))
if (foundTypeHandle.IsNull)
// if we can't find the MT (e.g in a minidump)
data->MTOfType = 0;
else if (rtsContract.IsFunctionPointer(foundTypeHandle, out _, out _) || rtsContract.IsPointer(foundTypeHandle))
data->MTOfType = rtsContract.GetPrimitiveType(CorElementType.U).Address.ToClrDataAddress(_target);
// array MTs
else if (rtsContract.IsArray(foundTypeHandle, out _))
Expand Down Expand Up @@ -1145,7 +1146,9 @@ int ISOSDacInterface.GetFieldDescData(ClrDataAddress fieldDesc, DacpFieldDescDat
data->bIsThreadLocal = rtsContract.IsFieldDescThreadStatic(fieldDescTargetPtr) ? 1 : 0;
data->bIsContextLocal = 0;
data->bIsStatic = rtsContract.IsFieldDescStatic(fieldDescTargetPtr) ? 1 : 0;
data->NextField = fieldDescTargetPtr + _target.GetTypeInfo(DataType.FieldDesc).Size!.Value;
data->NextField = rtsContract.TryGetFieldDescNext(fieldDescTargetPtr, out TargetPointer nextFieldDesc)
? nextFieldDesc.ToClrDataAddress(_target)
: 0;
}
catch (System.Exception ex)
{
Expand All @@ -1170,7 +1173,10 @@ int ISOSDacInterface.GetFieldDescData(ClrDataAddress fieldDesc, DacpFieldDescDat
Debug.Assert(data->bIsThreadLocal == dataLocal.bIsThreadLocal, $"cDAC: {data->bIsThreadLocal}, DAC: {dataLocal.bIsThreadLocal}");
Debug.Assert(data->bIsContextLocal == dataLocal.bIsContextLocal, $"cDAC: {data->bIsContextLocal}, DAC: {dataLocal.bIsContextLocal}");
Debug.Assert(data->bIsStatic == dataLocal.bIsStatic, $"cDAC: {data->bIsStatic}, DAC: {dataLocal.bIsStatic}");
Debug.Assert(data->NextField == dataLocal.NextField, $"cDAC: {data->NextField:x}, DAC: {dataLocal.NextField:x}");
// For the last field in a type, the legacy DAC returns a pointer one element past the end of
// the FieldDesc array (not a valid FieldDesc), whereas the cDAC's TryGetFieldDescNext reports
// no next field, which we surface as 0. Tolerate that intentional difference.
Debug.Assert(data->NextField == dataLocal.NextField || data->NextField == 0, $"cDAC: {data->NextField:x}, DAC: {dataLocal.NextField:x}");
}
}
#endif
Expand Down
51 changes: 51 additions & 0 deletions src/native/managed/cdac/tests/UnitTests/MethodTableTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1366,6 +1366,57 @@ public void GetFieldDescOffset_BigRVA_ResolvesFromMetadata(MockTarget.Architectu
Assert.Equal((uint)rva, contract.GetFieldDescOffset(fieldDescPtr, fieldDef));
}

[Theory]
[ClassData(typeof(MockTarget.StdArch))]
public void TryGetFieldDescNext_ReturnsNextFieldThenFalseAtEndOfList(MockTarget.Architecture arch)
{
const int numInstanceFields = 3;
const int numStaticFields = 2;
const int totalFields = numInstanceFields + numStaticFields;

TargetPointer typeMethodTablePtr = default;
TargetPointer fieldDescListStart = default;

TestPlaceholderTarget target = CreateTarget(
arch,
rtsBuilder =>
{
TargetTestHelpers targetTestHelpers = rtsBuilder.Builder.TargetTestHelpers;
TargetPointer systemObjectMethodTablePtr = rtsBuilder.SystemObjectMethodTable.Address;

MockEEClass eeClass = rtsBuilder.AddEEClass("EEClass WithFields");
eeClass.CorTypeAttr = (uint)(System.Reflection.TypeAttributes.Public | System.Reflection.TypeAttributes.Class);
eeClass.NumInstanceFields = numInstanceFields;
eeClass.NumStaticFields = numStaticFields;

MockMethodTable methodTable = rtsBuilder.AddMethodTable("MethodTable WithFields");
methodTable.BaseSize = targetTestHelpers.ObjectBaseSize;
methodTable.ParentMethodTable = systemObjectMethodTablePtr;
methodTable.NumVirtuals = 3;
eeClass.MethodTable = methodTable.Address;
methodTable.EEClassOrCanonMT = eeClass.Address;
typeMethodTablePtr = methodTable.Address;

fieldDescListStart = rtsBuilder.AddFieldDescList(methodTable.Address, totalFields);
eeClass.FieldDescList = fieldDescListStart.Value;
});

IRuntimeTypeSystem contract = target.Contracts.RuntimeTypeSystem;
TargetPointer[] fields = contract.GetFieldDescList(contract.GetTypeHandle(typeMethodTablePtr)).ToArray();
Assert.Equal(totalFields, fields.Length);
Assert.Equal(fieldDescListStart, fields[0]);

// Every field except the last advances to the following FieldDesc.
for (int i = 0; i < fields.Length - 1; i++)
{
Assert.True(contract.TryGetFieldDescNext(fields[i], out TargetPointer next));
Assert.Equal(fields[i + 1], next);
}
// The last field has no next FieldDesc, so the bounds check reports false.
Assert.False(contract.TryGetFieldDescNext(fields[^1], out TargetPointer last));
Assert.Equal(TargetPointer.Null, last);
}

// Builds a minimal metadata image containing a single static field with a FieldRVA row.
private static byte[] BuildMetadataWithRvaField(int rva, out FieldDefinitionHandle fieldHandle)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,18 @@ public ushort NumInstanceFields
set => WriteUInt16Field(NumInstanceFieldsFieldName, value);
}

public ushort NumStaticFields
{
get => ReadUInt16Field(NumStaticFieldsFieldName);
set => WriteUInt16Field(NumStaticFieldsFieldName, value);
}

public ulong FieldDescList
{
get => ReadPointerField(FieldDescListFieldName);
set => WritePointerField(FieldDescListFieldName, value);
}

public ushort NumNonVirtualSlots
{
get => ReadUInt16Field(NumNonVirtualSlotsFieldName);
Expand Down Expand Up @@ -541,6 +553,22 @@ internal MockFieldDesc AddFieldDesc(ulong mtOfEnclosingClass, CorElementType typ
return fieldDesc;
}

// Allocates `count` FieldDescs contiguously (matching the runtime's packed FieldDesc array), each
// recording `mtOfEnclosingClass`, and returns the address of the first one.
internal TargetPointer AddFieldDescList(ulong mtOfEnclosingClass, int count)
{
uint size = (uint)FieldDescLayout.Size;
MockMemorySpace.HeapFragment fragment = TypeSystemAllocator.Allocate((ulong)count * size, "FieldDesc array");
for (int i = 0; i < count; i++)
{
MockFieldDesc fieldDesc = FieldDescLayout.Create(
fragment.Data.AsMemory((int)((uint)i * size), (int)size),
fragment.Address + (uint)i * size);
fieldDesc.MTOfEnclosingClass = mtOfEnclosingClass;
}
return fragment.Address;
}

private TView Add<TView>(Layout<TView> layout, string name)
where TView : TypedView, new()
=> Add(layout, (ulong)layout.Size, name);
Expand Down