Skip to content
Draft
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
61 changes: 61 additions & 0 deletions docs/design/datacontracts/ObjectiveCMarshal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Contract ObjectiveCMarshal

This contract is for getting information related to Objective-C interop marshaling.

## APIs of contract

``` csharp
// Get the tagged memory for an Objective-C tracked reference object.
// Returns false if the object does not have tagged memory.
// On success, size is set to the size of the tagged memory in bytes; otherwise size is set to default.
bool TryGetTaggedMemory(TargetPointer address, out TargetNUInt size, out TargetPointer taggedMemory);
```

## Version 1

Data descriptors used:
| Data Descriptor Name | Field | Meaning |
| --- | --- | --- |
| `InteropSyncBlockInfo` | `TaggedMemory` | Pointer to the tagged memory for the object (if it exists) |
| `Object` | `m_pMethTab` | Method table for the object |
| `ObjectHeader` | `SyncBlockValue` | Sync block value for the object header |

Contracts used:
| Contract Name |
| --- |
| `SyncBlock` |

Globals used:
| Global Name | Type | Purpose |
| --- | --- | --- |
| `SyncBlockIsHashOrSyncBlockIndex` | uint32 | Bitmask: set if sync block value is a hash code or sync block index |
| `SyncBlockIsHashCode` | uint32 | Bitmask: set if sync block value is a hash code |
| `SyncBlockIndexMask` | uint32 | Mask to extract the sync block index from the sync block value |

``` csharp
bool TryGetTaggedMemory(TargetPointer address, out TargetNUInt size, out TargetPointer taggedMemory)
{
size = default;
taggedMemory = TargetPointer.Null;

ulong objectHeaderSize = /* ObjectHeader size */;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest factor out a little helper here that maps object address -> SyncBlock address and stick it on the object contract. The same algorithm shows up in getting BuiltInCom data and hashcode so far. As we keep going I expect you'll see at least two more usages of it for ENC added fields and monitor locks.

uint syncBlockValue = target.Read<uint>(address - objectHeaderSize + /* ObjectHeader::SyncBlockValue offset */);

// Check if the sync block value represents a sync block index
if ((syncBlockValue & (SyncBlockIsHashCode | SyncBlockIsHashOrSyncBlockIndex))
!= SyncBlockIsHashOrSyncBlockIndex)
return false;

uint index = syncBlockValue & SyncBlockIndexMask;
TargetPointer syncBlockPtr = target.Contracts.SyncBlock.GetSyncBlock(index);

TargetPointer interopInfoPtr = target.ReadPointer(syncBlockPtr + /* SyncBlock::InteropInfo offset */);
if (interopInfoPtr == TargetPointer.Null)
return false;

taggedMemory = target.ReadPointer(interopInfoPtr + /* InteropSyncBlockInfo::TaggedMemory offset */);
if (taggedMemory != TargetPointer.Null)
size = new TargetNUInt(2 * target.PointerSize);
return taggedMemory != TargetPointer.Null;
}
```
4 changes: 4 additions & 0 deletions docs/design/datacontracts/RuntimeTypeSystem.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ partial interface IRuntimeTypeSystem : IContract
public TargetPointer GetGCThreadStaticsBasePointer(TypeHandle typeHandle, TargetPointer threadPtr);
public TargetPointer GetNonGCThreadStaticsBasePointer(TypeHandle typeHandle, TargetPointer threadPtr);
public TargetPointer GetFieldDescList(TypeHandle typeHandle);
// True if the MethodTable represents a type tracked as an Objective-C reference type with a finalizer
public bool IsTrackedReferenceWithFinalizer(TypeHandle typeHandle);
public TargetPointer GetGCStaticsBasePointer(TypeHandle typeHandle);
public TargetPointer GetNonGCStaticsBasePointer(TypeHandle typeHandle);
public virtual ReadOnlySpan<TypeHandle> GetInstantiation(TypeHandle typeHandle);
Expand Down Expand Up @@ -535,6 +537,8 @@ Contracts used:

public TargetPointer GetFieldDescList(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? TargetPointer.Null : GetClassData(typeHandle).FieldDescList;

public bool IsTrackedReferenceWithFinalizer(TypeHandle typeHandle) => typeHandle.IsMethodTable() && _methodTables[typeHandle.Address].Flags.IsTrackedReferenceWithFinalizer;

public TargetPointer GetGCStaticsBasePointer(TypeHandle typeHandle)
{
if (!typeHandle.IsMethodTable())
Expand Down
6 changes: 6 additions & 0 deletions src/coreclr/vm/datadescriptor/datadescriptor.inc
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,9 @@ CDAC_TYPE_FIELD(InteropSyncBlockInfo, /*pointer*/, CCW, cdac_data<InteropSyncBlo
CDAC_TYPE_FIELD(InteropSyncBlockInfo, /*pointer*/, RCW, cdac_data<InteropSyncBlockInfo>::RCW)
CDAC_TYPE_FIELD(InteropSyncBlockInfo, /*pointer*/, CCF, cdac_data<InteropSyncBlockInfo>::CCF)
#endif // FEATURE_COMINTEROP
#ifdef FEATURE_OBJCMARSHAL
CDAC_TYPE_FIELD(InteropSyncBlockInfo, /*pointer*/, TaggedMemory, cdac_data<InteropSyncBlockInfo>::TaggedMemory)
#endif // FEATURE_OBJCMARSHAL
CDAC_TYPE_END(InteropSyncBlockInfo)

CDAC_TYPE_BEGIN(SyncBlock)
Expand Down Expand Up @@ -1428,6 +1431,9 @@ CDAC_GLOBAL_SUB_DESCRIPTOR(GC, &(g_gc_dac_vars.gc_descriptor))
#if FEATURE_COMINTEROP
CDAC_GLOBAL_CONTRACT(BuiltInCOM, 1)
#endif // FEATURE_COMINTEROP
#ifdef FEATURE_OBJCMARSHAL
CDAC_GLOBAL_CONTRACT(ObjectiveCMarshal, 1)
#endif // FEATURE_OBJCMARSHAL
CDAC_GLOBAL_CONTRACT(CodeVersions, 1)
#ifdef FEATURE_COMWRAPPERS
CDAC_GLOBAL_CONTRACT(ComWrappers, 1)
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/vm/methodtable.h
Original file line number Diff line number Diff line change
Expand Up @@ -3817,7 +3817,7 @@ public :

enum_flag_ContainsGCPointers = 0x01000000, // Contains object references. [cDAC] [RuntimeTypeSystem]: Contract depends on this value
enum_flag_HasTypeEquivalence = 0x02000000, // can be equivalent to another type
enum_flag_IsTrackedReferenceWithFinalizer = 0x04000000,
enum_flag_IsTrackedReferenceWithFinalizer = 0x04000000, // [cDAC] [RuntimeTypeSystem]: Contract depends on this value.
// unused = 0x08000000,

enum_flag_IDynamicInterfaceCastable = 0x10000000, // class implements IDynamicInterfaceCastable interface
Expand Down
4 changes: 4 additions & 0 deletions src/coreclr/vm/syncblk.h
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,7 @@ class InteropSyncBlockInfo
// should be updated as well.
// See the TAGGED_MEMORY_SIZE_IN_POINTERS constant in
// ObjectiveCMarshal.NativeAot.cs
// [cDAC] [ObjectiveCMarshal] : Contract depends on this size.
BYTE m_taggedAlloc[2 * sizeof(void*)];
#endif // FEATURE_OBJCMARSHAL

Expand All @@ -383,6 +384,9 @@ struct cdac_data<InteropSyncBlockInfo>
static constexpr size_t RCW = offsetof(InteropSyncBlockInfo, m_pRCW);
static constexpr size_t CCF = offsetof(InteropSyncBlockInfo, m_pCCF);
#endif // FEATURE_COMINTEROP
#ifdef FEATURE_OBJCMARSHAL
static constexpr size_t TaggedMemory = offsetof(InteropSyncBlockInfo, m_taggedMemory);
#endif // FEATURE_OBJCMARSHAL
};

typedef DPTR(InteropSyncBlockInfo) PTR_InteropSyncBlockInfo;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ public abstract class ContractRegistry
/// </summary>
public virtual IBuiltInCOM BuiltInCOM => GetContract<IBuiltInCOM>();
/// <summary>
/// Gets an instance of the ObjectiveCMarshal contract for the target.
/// </summary>
public virtual IObjectiveCMarshal ObjectiveCMarshal => GetContract<IObjectiveCMarshal>();
/// <summary>
/// Gets an instance of the ConditionalWeakTable contract for the target.
/// </summary>
public virtual IConditionalWeakTable ConditionalWeakTable => GetContract<IConditionalWeakTable>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;

namespace Microsoft.Diagnostics.DataContractReader.Contracts;

public interface IObjectiveCMarshal : IContract
{
static string IContract.Name { get; } = nameof(ObjectiveCMarshal);

// Get the tagged memory for an Objective-C tracked reference object.
// Returns false if the object does not have tagged memory.
// On success, size is set to the size of the tagged memory in bytes; otherwise size is set to default.
bool TryGetTaggedMemory(TargetPointer address, out TargetNUInt size, out TargetPointer taggedMemory) => throw new NotImplementedException();
}

public readonly struct ObjectiveCMarshal : IObjectiveCMarshal
{
// Everything throws NotImplementedException
}
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ public interface IRuntimeTypeSystem : IContract
ushort GetNumStaticFields(TypeHandle typeHandle) => throw new NotImplementedException();
ushort GetNumThreadStaticFields(TypeHandle typeHandle) => throw new NotImplementedException();
TargetPointer GetFieldDescList(TypeHandle typeHandle) => throw new NotImplementedException();
// True if the MethodTable represents a type tracked as an Objective-C reference type with a finalizer
bool IsTrackedReferenceWithFinalizer(TypeHandle typeHandle) => throw new NotImplementedException();
TargetPointer GetGCStaticsBasePointer(TypeHandle typeHandle) => throw new NotImplementedException();
TargetPointer GetNonGCStaticsBasePointer(TypeHandle typeHandle) => throw new NotImplementedException();
TargetPointer GetGCThreadStaticsBasePointer(TypeHandle typeHandle, TargetPointer threadPtr) => throw new NotImplementedException();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.Diagnostics.DataContractReader.Contracts;

public sealed class ObjectiveCMarshalFactory : IContractFactory<IObjectiveCMarshal>
{
IObjectiveCMarshal IContractFactory<IObjectiveCMarshal>.CreateContract(Target target, int version)
{
uint syncBlockIsHashOrSyncBlockIndex = target.ReadGlobal<uint>(Constants.Globals.SyncBlockIsHashOrSyncBlockIndex);
uint syncBlockIsHashCode = target.ReadGlobal<uint>(Constants.Globals.SyncBlockIsHashCode);
uint syncBlockIndexMask = target.ReadGlobal<uint>(Constants.Globals.SyncBlockIndexMask);
return version switch
{
1 => new ObjectiveCMarshal_1(target, syncBlockIsHashOrSyncBlockIndex, syncBlockIsHashCode, syncBlockIndexMask),
_ => default(ObjectiveCMarshal),
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.Diagnostics.DataContractReader.Contracts;

internal readonly struct ObjectiveCMarshal_1 : IObjectiveCMarshal
{
private readonly Target _target;
private readonly uint _syncBlockIsHashOrSyncBlockIndex;
private readonly uint _syncBlockIsHashCode;
private readonly uint _syncBlockIndexMask;

internal ObjectiveCMarshal_1(Target target,
uint syncBlockIsHashOrSyncBlockIndex, uint syncBlockIsHashCode, uint syncBlockIndexMask)
{
_target = target;
_syncBlockIsHashOrSyncBlockIndex = syncBlockIsHashOrSyncBlockIndex;
_syncBlockIsHashCode = syncBlockIsHashCode;
_syncBlockIndexMask = syncBlockIndexMask;
}

public bool TryGetTaggedMemory(TargetPointer address, out TargetNUInt size, out TargetPointer taggedMemory)
{
size = default;
taggedMemory = TargetPointer.Null;

ulong objectHeaderSize = _target.GetTypeInfo(DataType.ObjectHeader).Size!.Value;
Data.ObjectHeader header = _target.ProcessedData.GetOrAdd<Data.ObjectHeader>(address - objectHeaderSize);
uint syncBlockValue = header.SyncBlockValue;

// Check if the sync block value represents a sync block index
if ((syncBlockValue & (_syncBlockIsHashCode | _syncBlockIsHashOrSyncBlockIndex))
!= _syncBlockIsHashOrSyncBlockIndex)
return false;

uint index = syncBlockValue & _syncBlockIndexMask;
TargetPointer syncBlock = _target.Contracts.SyncBlock.GetSyncBlock(index);
Data.SyncBlock sb = _target.ProcessedData.GetOrAdd<Data.SyncBlock>(syncBlock);

taggedMemory = sb.InteropInfo?.TaggedMemory ?? TargetPointer.Null;
if (taggedMemory != TargetPointer.Null)
size = new TargetNUInt(2 * (ulong)_target.PointerSize);
return taggedMemory != TargetPointer.Null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,7 @@ public ushort GetNumVtableSlots(TypeHandle typeHandle)
public ushort GetNumStaticFields(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? (ushort)0 : GetClassData(typeHandle).NumStaticFields;
public ushort GetNumThreadStaticFields(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? (ushort)0 : GetClassData(typeHandle).NumThreadStaticFields;
public TargetPointer GetFieldDescList(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? TargetPointer.Null : GetClassData(typeHandle).FieldDescList;
public bool IsTrackedReferenceWithFinalizer(TypeHandle typeHandle) => typeHandle.IsMethodTable() && _methodTables[typeHandle.Address].Flags.IsTrackedReferenceWithFinalizer;
private TargetPointer GetDynamicStaticsInfo(TypeHandle typeHandle)
{
if (!typeHandle.IsMethodTable())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,13 @@ public InteropSyncBlockInfo(Target target, TargetPointer address)
CCF = type.Fields.TryGetValue(nameof(CCF), out Target.FieldInfo ccfField)
? target.ReadPointer(address + (ulong)ccfField.Offset)
: TargetPointer.Null;
TaggedMemory = type.Fields.TryGetValue(nameof(TaggedMemory), out Target.FieldInfo taggedMemoryField)
? target.ReadPointer(address + (ulong)taggedMemoryField.Offset)
: TargetPointer.Null;
}

public TargetPointer RCW { get; init; }
public TargetPointer CCW { get; init; }
public TargetPointer CCF { get; init; }
public TargetPointer TaggedMemory { get; init; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ internal enum WFLAGS_HIGH : uint
Collectible = 0x00200000, // GC depends on this bit.

ContainsGCPointers = 0x01000000,
IsTrackedReferenceWithFinalizer = 0x04000000,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to add this to the RuntimeTypeSystem.md markdown file.

HasComponentSize = 0x80000000, // This is set if lower 16 bits is used for the component size,
// otherwise the lower bits are used for WFLAGS_LOW
}
Expand Down Expand Up @@ -99,6 +100,7 @@ private bool TestFlagWithMask(WFLAGS2_ENUM mask, WFLAGS2_ENUM flag)
public bool HasInstantiation => !TestFlagWithMask(WFLAGS_LOW.GenericsMask, WFLAGS_LOW.GenericsMask_NonGeneric);
public bool ContainsGCPointers => GetFlag(WFLAGS_HIGH.ContainsGCPointers) != 0;
public bool IsCollectible => GetFlag(WFLAGS_HIGH.Collectible) != 0;
public bool IsTrackedReferenceWithFinalizer => GetFlag(WFLAGS_HIGH.IsTrackedReferenceWithFinalizer) != 0;
public bool IsDynamicStatics => GetFlag(WFLAGS2_ENUM.DynamicStatics) != 0;
public bool IsGenericTypeDefinition => TestFlagWithMask(WFLAGS_LOW.GenericsMask, WFLAGS_LOW.GenericsMask_TypicalInstantiation);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5663,9 +5663,123 @@ int ISOSDacInterface10.GetComWrappersRCWData(ClrDataAddress rcw, ClrDataAddress*

#region ISOSDacInterface11
int ISOSDacInterface11.IsTrackedType(ClrDataAddress objAddr, Interop.BOOL* isTrackedType, Interop.BOOL* hasTaggedMemory)
=> _legacyImpl11 is not null ? _legacyImpl11.IsTrackedType(objAddr, isTrackedType, hasTaggedMemory) : HResults.E_NOTIMPL;
{
int hr = HResults.S_OK;
try
{
if (objAddr == 0 || isTrackedType == null || hasTaggedMemory == null)
{
throw new ArgumentException();
}
else
{

*isTrackedType = Interop.BOOL.FALSE;
*hasTaggedMemory = Interop.BOOL.FALSE;
IObject objectContract = _target.Contracts.Object;
TargetPointer objPtr = objAddr.ToTargetPointer(_target);
TargetPointer mt = objectContract.GetMethodTableAddress(objPtr);
IRuntimeTypeSystem rtsContract = _target.Contracts.RuntimeTypeSystem;
TypeHandle mtHandle = rtsContract.GetTypeHandle(mt);
if (mt == TargetPointer.Null)
{
throw new ArgumentException();
}
else
{
if (rtsContract.IsTrackedReferenceWithFinalizer(mtHandle))
*isTrackedType = Interop.BOOL.TRUE;
hr = (*isTrackedType == Interop.BOOL.TRUE) ? HResults.S_OK : HResults.S_FALSE;
try
{
IObjectiveCMarshal objcContract = _target.Contracts.ObjectiveCMarshal;
if (objcContract.TryGetTaggedMemory(objPtr, out _, out _))
*hasTaggedMemory = Interop.BOOL.TRUE;
}
catch (NotImplementedException)
{
// TryGetTaggedMemory may not be implemented if ObjectiveCMarshal contract is not present
}
}
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl11 is not null)
{
Interop.BOOL isTrackedTypeLocal;
Interop.BOOL hasTaggedMemoryLocal;
int hrLocal = _legacyImpl11.IsTrackedType(objAddr, &isTrackedTypeLocal, &hasTaggedMemoryLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK || hr == HResults.S_FALSE)
{
Debug.Assert(*isTrackedType == isTrackedTypeLocal);
Debug.Assert(*hasTaggedMemory == hasTaggedMemoryLocal);
}
}
#endif
return hr;
}

int ISOSDacInterface11.GetTaggedMemory(ClrDataAddress objAddr, ClrDataAddress* taggedMemory, nuint* taggedMemorySizeInBytes)
=> _legacyImpl11 is not null ? _legacyImpl11.GetTaggedMemory(objAddr, taggedMemory, taggedMemorySizeInBytes) : HResults.E_NOTIMPL;
{
int hr = HResults.S_OK;
try
{
if (objAddr == 0 || taggedMemory == null || taggedMemorySizeInBytes == null)
{
throw new ArgumentException();
}
else
{
*taggedMemory = 0;
*taggedMemorySizeInBytes = 0;

TargetPointer objPtr = objAddr.ToTargetPointer(_target);
try
{
IObjectiveCMarshal objcContract = _target.Contracts.ObjectiveCMarshal;
if (objcContract.TryGetTaggedMemory(objPtr, out TargetNUInt taggedMemorySizeNUInt, out TargetPointer taggedMemoryPtr))
{
*taggedMemory = taggedMemoryPtr.ToClrDataAddress(_target);
*taggedMemorySizeInBytes = (nuint)taggedMemorySizeNUInt.Value;
}
else
{
hr = HResults.S_FALSE;
}
}
catch (NotImplementedException)
{
// TryGetTaggedMemory may not be implemented if ObjectiveCMarshal contract is not present
hr = HResults.S_FALSE;
}
}
}

catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl11 is not null)
{
ClrDataAddress taggedMemoryLocal;
nuint taggedMemorySizeInBytesLocal;
int hrLocal = _legacyImpl11.GetTaggedMemory(objAddr, &taggedMemoryLocal, &taggedMemorySizeInBytesLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK || hr == HResults.S_FALSE)
{
Debug.Assert(*taggedMemory == taggedMemoryLocal);
Debug.Assert(*taggedMemorySizeInBytes == taggedMemorySizeInBytesLocal);
}
}
#endif
return hr;
}
#endregion ISOSDacInterface11

#region ISOSDacInterface12
Expand Down
Loading
Loading