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
33 changes: 20 additions & 13 deletions docs/design/datacontracts/ComWrappers.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ TargetPointer GetComWrappersRCWForObject(TargetPointer obj);

## Version 1

Data descriptors used:
### Data descriptors used

| Data Descriptor Name | Field | Meaning |
| --- | --- | --- |
| `NativeObjectWrapperObject` | `ExternalComObject` | Address of the external COM object |
Expand All @@ -40,26 +41,33 @@ Data descriptors used:
| `InternalComInterfaceDispatch` | `Entries` | Start of vtable entry pointers within the dispatch block |
| `ComWrappersVtablePtrs` | `Size` | Size of vtable pointers array |

Global variables used:
### Global variables used

| Global Name | Type | Purpose |
| --- | --- | --- |
| `ComWrappersVtablePtrs` | TargetPointer | Pointer to struct containing ComWrappers-related function pointers |
| `DispatchThisPtrMask` | TargetPointer | Used to mask low bits of CCW pointer to the nearest valid address from which to read a managed object wrapper |

### Contract Constants:
### Managed types used

| Fully-qualified name | Module | Members read | Purpose |
| --- | --- | --- | --- |
| `System.Runtime.InteropServices.ComWrappers+NativeObjectWrapper` | `System.Private.CoreLib` | (type layout only) | Identifies RCWs by comparing their MethodTable against this type |
| `System.Runtime.InteropServices.ComWrappers` | `System.Private.CoreLib` | `static s_allManagedObjectWrapperTable`, `static s_nativeObjectWrapperTable` | Per-object lookups for managed-object-wrapper enumeration and RCW retrieval |

### Contract Constants

| Name | Type | Purpose | Value |
| --- | --- | --- | --- |
| `NativeObjectWrapperNamespace` | string | Namespace of System.Runtime.InteropServices.ComWrappers+NativeObjectWrapper | `System.Runtime.InteropServices` |
| `NativeObjectWrapperName` | string | Name of System.Runtime.InteropServices.ComWrappers+NativeObjectWrapper | `ComWrappers+NativeObjectWrapper` |
| `CallerDefinedIUnknown` | int | Flag bit for `CreateComInterfaceFlagsEx` indicating caller-defined IUnknown | `1` |
| `IID_IUnknown` | Guid | The IID for IUnknown | `00000000-0000-0000-C000-000000000046` |

Contracts used:
### Contracts used

| Contract Name |
| --- |
| `Object` |
| `RuntimeTypeSystem` |
| `Loader` |
| `ManagedTypeSource` |
| `ConditionalWeakTable` |


Expand Down Expand Up @@ -157,22 +165,21 @@ public TargetPointer GetIdentityForMOW(TargetPointer mow)

public List<TargetPointer> GetMOWs(TargetPointer obj, out bool hasMOWTable)
{
// Look up the static field ComWrappers.s_allManagedObjectWrapperTable via RuntimeTypeSystem
// Look up the static field ComWrappers.s_allManagedObjectWrapperTable via ManagedTypeSource
// Use the ConditionalWeakTable contract to find the List<ManagedObjectWrapperHolderObject> value
// Iterate the list and return each holder's Wrapper pointer (the ManagedObjectWrapperLayout address)
}

public bool IsComWrappersRCW(TargetPointer rcw)
{
// Get method table from rcw using Object contract GetMethodTableAddress
// Find module from the system assembly
// Then use RuntimeTypeSystem contract to look up type handle by name/namespace hardcoded in contract
// Then compare the rcw method table with the method table found by name/namespace/module
// Resolve ComWrappers+NativeObjectWrapper via ManagedTypeSource by fully-qualified name
// Then compare the rcw method table with the method table found by name
}

public TargetPointer GetComWrappersRCWForObject(TargetPointer obj)
{
// Look up the static field ComWrappers.s_nativeObjectWrapperTable via RuntimeTypeSystem
// Look up the static field ComWrappers.s_nativeObjectWrapperTable via ManagedTypeSource
// Use the ConditionalWeakTable contract to find the value associated with obj
// If found, return the NativeObjectWrapper reference (tagged with low bit by caller)
TargetPointer cwtTable = /* address of ComWrappers.s_nativeObjectWrapperTable static field */;
Expand Down
68 changes: 28 additions & 40 deletions docs/design/datacontracts/ConditionalWeakTable.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,67 +13,55 @@ bool TryGetValue(TargetPointer conditionalWeakTable, TargetPointer key, out Targ
## Version 1

This contract reads the field layout of `ConditionalWeakTable<TKey, TValue>` and its nested types
(`Container`, `Container+Entry`) via the `RuntimeTypeSystem` contract rather than cDAC data descriptors.
Field offsets are resolved by name at runtime.
(`Container`, `Container+Entry`) via the [`ManagedTypeSource`](ManagedTypeSource.md) contract rather
than cDAC data descriptors. Field offsets are resolved by name at runtime.

### Data descriptors used

Contract constants:
| Constant | Value | Meaning |
| --- | --- | --- |
| `CWTNamespace` | `System.Runtime.CompilerServices` | Namespace of the `ConditionalWeakTable` type |
| `CWTTypeName` | ``ConditionalWeakTable`2`` | Name of the `ConditionalWeakTable<TKey, TValue>` type |
| `ContainerTypeName` | ``ConditionalWeakTable`2+Container`` | Name of the nested `Container` type |
| `EntryTypeName` | ``ConditionalWeakTable`2+Entry`` | Name of the nested `Entry` value type |
| `ContainerFieldName` | `_container` | Field on `ConditionalWeakTable` pointing to the active container |
| `BucketsFieldName` | `_buckets` | Field on `Container` pointing to the `int[]` buckets array |
| `EntriesFieldName` | `_entries` | Field on `Container` pointing to the `Entry[]` entries array |
| `HashCodeFieldName` | `HashCode` | Field on `Entry` storing the hash code (masked to positive int) |
| `NextFieldName` | `Next` | Field on `Entry` storing the next index in the chain, or -1 |
| `DepHndFieldName` | `depHnd` | Field on `Entry` storing the dependent handle |

Data descriptors used:
| Data Descriptor Name | Field | Meaning |
| --- | --- | --- |
| `Array` | `m_NumComponents` | Number of elements in the array |

Contracts used:
### Managed types used

| Fully-qualified name | Module | Members read | Purpose |
| --- | --- | --- | --- |
| ``System.Runtime.CompilerServices.ConditionalWeakTable`2`` | `System.Private.CoreLib` | `_container` | Pointer to the active container |
| ``System.Runtime.CompilerServices.ConditionalWeakTable`2+Container`` | `System.Private.CoreLib` | `_buckets`, `_entries` | `int[]` bucket map and `Entry[]` storage |
| ``System.Runtime.CompilerServices.ConditionalWeakTable`2+Entry`` | `System.Private.CoreLib` | `HashCode`, `Next`, `depHnd` | Hash code, next-in-chain index, and dependent handle for each entry |

### Contracts used

| Contract Name |
| --- |
| `Object` |
| `GC` |
| `ManagedTypeSource` |
| `RuntimeTypeSystem` |

The algorithm looks up the `_container` field of the `ConditionalWeakTable` object, then reads the
`_buckets` and `_entries` fields from the container. It resolves `Entry` field offsets (`HashCode`,
`Next`, `depHnd`) via `RuntimeTypeSystem` and determines the entry stride from the entries array's
`Next`, `depHnd`) via `ManagedTypeSource` and determines the entry stride from the entries array's
component size.

``` csharp
bool TryGetValue(TargetPointer conditionalWeakTable, TargetPointer key, out TargetPointer value)
{
value = TargetPointer.Null;

// Resolve field offsets by name from CoreLib via RuntimeTypeSystem.
// GetCoreLibFieldDescAndDef returns a FieldDesc address and FieldDefinition;
// GetFieldDescOffset extracts the byte offset from those.
// Resolve field offsets by name via ManagedTypeSource.
IRuntimeTypeSystem rts = target.Contracts.RuntimeTypeSystem;

rts.GetCoreLibFieldDescAndDef(CWTNamespace, CWTTypeName, ContainerFieldName, out fd, out fDef);
uint containerOffset = rts.GetFieldDescOffset(fd, fDef);

rts.GetCoreLibFieldDescAndDef(CWTNamespace, ContainerTypeName, BucketsFieldName, out fd, out fDef);
uint bucketsOffset = rts.GetFieldDescOffset(fd, fDef);

rts.GetCoreLibFieldDescAndDef(CWTNamespace, ContainerTypeName, EntriesFieldName, out fd, out fDef);
uint entriesOffset = rts.GetFieldDescOffset(fd, fDef);

rts.GetCoreLibFieldDescAndDef(CWTNamespace, EntryTypeName, HashCodeFieldName, out fd, out fDef);
uint hashCodeOffset = rts.GetFieldDescOffset(fd, fDef);

rts.GetCoreLibFieldDescAndDef(CWTNamespace, EntryTypeName, NextFieldName, out fd, out fDef);
uint nextOffset = rts.GetFieldDescOffset(fd, fDef);

rts.GetCoreLibFieldDescAndDef(CWTNamespace, EntryTypeName, DepHndFieldName, out fd, out fDef);
uint depHndOffset = rts.GetFieldDescOffset(fd, fDef);
IManagedTypeSource mts = target.Contracts.ManagedTypeSource;
Target.TypeInfo cwtType = mts.GetTypeInfo("System.Runtime.CompilerServices.ConditionalWeakTable`2");
Target.TypeInfo containerType = mts.GetTypeInfo("System.Runtime.CompilerServices.ConditionalWeakTable`2+Container");
Target.TypeInfo entryType = mts.GetTypeInfo("System.Runtime.CompilerServices.ConditionalWeakTable`2+Entry");

uint containerOffset = (uint)cwtType.Fields["_container"].Offset;
uint bucketsOffset = (uint)containerType.Fields["_buckets"].Offset;
uint entriesOffset = (uint)containerType.Fields["_entries"].Offset;
uint hashCodeOffset = (uint)entryType.Fields["HashCode"].Offset;
uint nextOffset = (uint)entryType.Fields["Next"].Offset;
uint depHndOffset = (uint)entryType.Fields["depHnd"].Offset;

// Navigate from the ConditionalWeakTable object to its container
TargetPointer container = target.ReadPointer(conditionalWeakTable + /* Object data offset */ + containerOffset);
Expand Down
200 changes: 200 additions & 0 deletions docs/design/datacontracts/ManagedTypeSource.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
# Contract ManagedTypeSource

Resolves the runtime layout of managed CLR types and the addresses of their
static and thread-static fields by fully-qualified name. Consumers use this
contract when an algorithm needs to read a managed type that is defined in the
assemblies (e.g. `System.Threading.Lock`, `System.Runtime.CompilerServices.ConditionalWeakTable<TKey, TValue>+Container`) rather than via a native data descriptor.

All lookups are performed against `System.Private.CoreLib` (the runtime's
system assembly). Assembly forwarders are not followed. Nested types are
addressed by separating the outer and inner type names with `+` (matching
ECMA-335 / `Type.FullName` conventions), e.g.
`System.Runtime.InteropServices.ComWrappers+NativeObjectWrapper`.

## APIs of contract

``` csharp
// Return true and populate `info` with the instance-field layout of the type, or
// false if the type cannot be resolved.
bool TryGetTypeInfo(string fullyQualifiedName, out Target.TypeInfo info);

// Throws InvalidOperationException if the type cannot be resolved.
Target.TypeInfo GetTypeInfo(string fullyQualifiedName);

// Return true and populate `typeHandle` with the runtime TypeHandle for the type,
// or false if the type cannot be resolved.
bool TryGetTypeHandle(string fullyQualifiedName, out TypeHandle typeHandle);
TypeHandle GetTypeHandle(string fullyQualifiedName);

// Return true and populate `address` with the address of the named static field,
// or false if the type / field cannot be resolved or its statics storage has not
// been allocated. Returns false for thread-static fields — use the thread-static
// API for those.
bool TryGetStaticFieldAddress(string fullyQualifiedName, string fieldName, out TargetPointer address);
TargetPointer GetStaticFieldAddress(string fullyQualifiedName, string fieldName);

// Return true and populate `address` with the per-thread address of the named
// thread-static field on the supplied `thread`, or false if the type / field
// cannot be resolved or per-thread storage has not been allocated for the
// enclosing class on this thread. Returns false for non-thread-static fields.
bool TryGetThreadStaticFieldAddress(string fullyQualifiedName, string fieldName, TargetPointer thread, out TargetPointer address);
TargetPointer GetThreadStaticFieldAddress(string fullyQualifiedName, string fieldName, TargetPointer thread);
```

## Version 1

### Data descriptors used

Data descriptors used: none

### Global variables used

Global variables used: none

### Managed types used

The contract does not itself enumerate a fixed set of managed types — the
caller supplies the FQN. Consumers should document the specific managed types
they read in their own `### Managed types used` section.

### Contracts used

| Contract Name |
| --- |
| `Loader` |
| `EcmaMetadata` |
| `RuntimeTypeSystem` |

``` csharp
// Type resolution: parse the fully-qualified name, walk System.Private.CoreLib's
// metadata to locate the TypeDef, then map TypeDef -> MethodTable via the loader.
bool TryResolveType(string managedFqName, out TypeHandle th, out MetadataReader mdReader, out TypeDefinition typeDef)
{
ILoader loader = target.Contracts.Loader;
TargetPointer systemAssembly = loader.GetSystemAssembly();
ModuleHandle moduleHandle = loader.GetModuleHandleFromAssemblyPtr(systemAssembly);

mdReader = target.Contracts.EcmaMetadata.GetMetadata(moduleHandle);
if (mdReader == null)
return false;

// Split outer+nested segments on '+'. The outer segment is matched against
// (Namespace + "." + Name); nested segments are matched against the Name of
// GetNestedTypes() entries.
if (!TryFindTypeDefinition(mdReader, managedFqName, out TypeDefinitionHandle typeDefHandle))
return false;

// Resolve the TypeDef token via the module's TypeDef -> MethodTable map.
int token = MetadataTokens.GetToken(typeDefHandle);
TargetPointer typeDefToMT = loader.GetLookupTables(moduleHandle).TypeDefToMethodTable;
TargetPointer mt = loader.GetModuleLookupMapElement(typeDefToMT, (uint)token, out _);
if (mt == TargetPointer.Null)
return false;

th = target.Contracts.RuntimeTypeSystem.GetTypeHandle(mt);
typeDef = mdReader.GetTypeDefinition(typeDefHandle);
return true;
}

bool TryGetTypeHandle(string fqn, out TypeHandle th)
{
return TryResolveType(fqn, out th, out _, out _);
}

bool TryGetTypeInfo(string fqn, out Target.TypeInfo info)
{
if (!TryResolveType(fqn, out TypeHandle th, out MetadataReader mdReader, out TypeDefinition typeDef))
return false;

IRuntimeTypeSystem rts = target.Contracts.RuntimeTypeSystem;
Dictionary<string, Target.FieldInfo> fields = new();

foreach (FieldDefinitionHandle fh in typeDef.GetFields())
{
FieldDefinition fd = mdReader.GetFieldDefinition(fh);
if ((fd.Attributes & FieldAttributes.Static) != 0)
continue;

string fieldName = mdReader.GetString(fd.Name);
TargetPointer fdAddr = rts.GetFieldDescByName(th, fieldName);
if (fdAddr == TargetPointer.Null)
continue;

uint offset = rts.GetFieldDescOffset(fdAddr, fd);
CorElementType et = rts.GetFieldDescType(fdAddr);
// Raw field offset relative to the instance data start. Reference-type
// consumers must add the object header size; value-type consumers (e.g.
// struct entries embedded in arrays) read directly from the slot.
fields[fieldName] = new Target.FieldInfo { Offset = (int)offset, TypeName = MapElementType(et) };
}
info = new Target.TypeInfo { Fields = fields };
return true;
}

bool TryGetStaticFieldAddress(string fqn, string fieldName, out TargetPointer address)
{
address = TargetPointer.Null;
if (!TryGetFieldDesc(fqn, fieldName, out TargetPointer fdAddr))
return false;

IRuntimeTypeSystem rts = target.Contracts.RuntimeTypeSystem;

// Thread-statics return a per-thread offset, not an absolute address.
if (rts.IsFieldDescThreadStatic(fdAddr))
return false;

// Gate on the statics base being allocated for the enclosing class so callers
// cannot dereference a small offset-from-zero when the class has not been
// initialized.
TargetPointer enclosingMT = rts.GetMTOfEnclosingClass(fdAddr);
TypeHandle ctx = rts.GetTypeHandle(enclosingMT);
CorElementType et = rts.GetFieldDescType(fdAddr);
bool isGC = et is CorElementType.Class or CorElementType.ValueType;
TargetPointer @base = isGC
? rts.GetGCStaticsBasePointer(ctx)
: rts.GetNonGCStaticsBasePointer(ctx);
if (@base == TargetPointer.Null)
return false;

address = rts.GetFieldDescStaticAddress(fdAddr);
return true;
}

bool TryGetThreadStaticFieldAddress(string fqn, string fieldName, TargetPointer thread, out TargetPointer address)
{
address = TargetPointer.Null;
if (!TryGetFieldDesc(fqn, fieldName, out TargetPointer fdAddr))
return false;

IRuntimeTypeSystem rts = target.Contracts.RuntimeTypeSystem;
if (!rts.IsFieldDescThreadStatic(fdAddr))
return false;

// Gate on the per-thread base being allocated for the enclosing class on this
// thread so callers cannot dereference a small offset-from-zero when this
// thread has not initialized thread-static storage for the type.
TargetPointer enclosingMT = rts.GetMTOfEnclosingClass(fdAddr);
TypeHandle ctx = rts.GetTypeHandle(enclosingMT);
CorElementType et = rts.GetFieldDescType(fdAddr);
bool isGC = et is CorElementType.Class or CorElementType.ValueType;
TargetPointer @base = isGC
? rts.GetGCThreadStaticsBasePointer(ctx, thread)
: rts.GetNonGCThreadStaticsBasePointer(ctx, thread);
if (@base == TargetPointer.Null)
return false;

address = rts.GetFieldDescThreadStaticAddress(fdAddr, thread);
return true;
}

bool TryGetFieldDesc(string fqn, string fieldName, out TargetPointer fdAddr)
{
if (!TryResolveType(fqn, out TypeHandle th, out _, out _))
{
fdAddr = TargetPointer.Null;
return false;
}
fdAddr = target.Contracts.RuntimeTypeSystem.GetFieldDescByName(th, fieldName);
return fdAddr != TargetPointer.Null;
}
```
Loading
Loading