Skip to content
Merged
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
255da99
Progress towards GetMethodTableName
davidwrighton Jul 9, 2024
0671cd4
Move metadata reader into helpers
davidwrighton Jul 11, 2024
636bb2b
Simplify storage of saved metadata copy for reflection emit scenarios
davidwrighton Jul 11, 2024
d359ed9
Fix bugs found when trying to actually use this logic
davidwrighton Jul 11, 2024
fc24cd4
Update contract documentation
davidwrighton Jul 11, 2024
d93e98b
Merge branch 'main' into cdac-methodtable-name
davidwrighton Jul 11, 2024
fead409
Feedback
davidwrighton Jul 12, 2024
d2b36fc
Add support for the EEName DacStream
davidwrighton Jul 15, 2024
8fdcc24
- Refactor to expose TypeHandle exclusively from the contracts
davidwrighton Jul 15, 2024
30da32c
Address concern around data structure for DynamicMetadata
davidwrighton Jul 15, 2024
87f7003
Merge branch 'cdac-methodtable-name' of github.com:davidwrighton/runt…
davidwrighton Jul 15, 2024
143cb86
Fix build break
davidwrighton Jul 15, 2024
7284f83
Fix musl build issue
davidwrighton Jul 16, 2024
2d5ec4f
Cherrypick RuntimeTypeSystem changes from #104759
lambdageek Jul 16, 2024
8e2dbca
update tests
lambdageek Jul 16, 2024
e3964f9
start GetMethodDescDataImpl
lambdageek Jul 8, 2024
38d0a51
WIP: managed GetMethodDescData skeleton
lambdageek Jul 8, 2024
9d88f19
wip: MethodDesc
lambdageek Jul 9, 2024
4a4c3a4
add MethodDescChunk
lambdageek Jul 10, 2024
510a10f
WIP: validating a MethodDesc
lambdageek Jul 11, 2024
419cb11
checkpoint: MethodDesc validation
lambdageek Jul 12, 2024
1321aa9
update contract
lambdageek Jul 12, 2024
dcc4540
fix RuntimeTypeSystem unit tests
lambdageek Jul 12, 2024
c31bdcf
update contract
lambdageek Jul 15, 2024
7517e32
fix GetMethodDescChunkPointerMayThrow
lambdageek Jul 15, 2024
54ab23e
update tests
lambdageek Jul 16, 2024
27fcddc
fixup rebase
lambdageek Jul 16, 2024
2a41206
Remove unnecessary usings
davidwrighton Jul 16, 2024
dbe3cf4
- Transform magic numbers from DacStreams implementation into constants
davidwrighton Jul 16, 2024
3a35f41
Feedback
davidwrighton Jul 16, 2024
1eb3b36
Merge branch 'cdac-methodtable-name' into cdac-methoddescname
davidwrighton Jul 17, 2024
785ced4
Add non-contract portion of GetMethodDescName
davidwrighton Jul 19, 2024
01d3d24
Remove default parameter from AppendMethodInternal
davidwrighton Aug 7, 2024
1b6b6fd
Merge branch 'main' of github.com:dotnet/runtime into cdac-methoddesc…
davidwrighton Aug 7, 2024
0c7a254
First stab at the actual contract implementations for the MethodDesc …
davidwrighton Aug 8, 2024
ec9eb54
Commit current state
davidwrighton Aug 8, 2024
54ded9a
It Works!
davidwrighton Aug 8, 2024
3ffb03b
Document the new contracts needed for generating MethodDesc names
davidwrighton Aug 8, 2024
e72cea6
Handle Path fallback scenario. Leave actual implementation/documentat…
davidwrighton Aug 9, 2024
c250bb8
Remove extra lines found in Loader_1.cs
davidwrighton Aug 9, 2024
c3ebfdb
Address code review comments
davidwrighton Aug 9, 2024
bcf9fba
Adjust cdac naming to new model
davidwrighton Aug 12, 2024
de846a5
Merge branch 'main' into cdac-methoddescname
davidwrighton Aug 12, 2024
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
253 changes: 245 additions & 8 deletions docs/design/datacontracts/RuntimeTypeSystem.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,49 @@ struct MethodDescHandle
}
```

```csharp
public enum ArrayFunctionType
{
Get = 0,
Set = 1,
Address = 2,
Constructor = 3
}
```

```csharp
partial interface IRuntimeTypeSystem : IContract
{
public virtual MethodDescHandle GetMethodDescHandle(TargetPointer methodDescPointer);

public virtual TargetPointer GetMethodTable(MethodDescHandle methodDesc);

// Return true for an uninstantiated generic method
public virtual bool IsGenericMethodDefinition(MethodDescHandle methodDesc);

public virtual ReadOnlySpan<TypeHandle> GetGenericMethodInstantiation(MethodDescHandle methodDesc);

// Return mdTokenNil (0x06000000) if the method doesn't have a token, otherwise return the token of the method
public virtual uint GetMethodToken(MethodDescHandle methodDesc);

// Return true if a MethodDesc represents an array method
// An array method is also a StoredSigMethodDesc
public virtual bool IsArrayMethod(MethodDescHandle methodDesc, out ArrayFunctionType functionType);

// Return true if a MethodDesc represents a dynamically generated method, either an IL Stub dynamically
// generated by the runtime, or a MethodDesc that describes a method represented by the System.Reflection.Emit.DynamicMethod class
// A dynamic method is also a StoredSigMethodDesc
public virtual bool IsDynamicMethod(MethodDescHandle methodDesc, out ReadOnlySpan<byte> methodName);
Copy link
Member

Choose a reason for hiding this comment

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

Would it make sense to just make methodName a string instead of ReadOnlySpan<byte>, so the consumer doesn't have to know the encoding?

Copy link
Member Author

Choose a reason for hiding this comment

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

Not a bad idea. In general, I don't want to expose arrays to contract users, as its far to easy to mutate them, but strings are nice in that they are our only truly immutable type in the BCL, and work nicely for this sort of thing.


// A StoredSigMethodDesc is a MethodDesc for which the signature isn't found in metadata.
public virtual bool IsStoredSigMethodDesc(MethodDescHandle methodDesc, out ReadOnlySpan<byte> signature);

// Return true for a MethodDesc that describes a method represented by the System.Reflection.Emit.DynamicMethod class
// A LCG method is also a StoredSigMethodDesc
public virtual bool IsLCGMethod(MethodDescHandle methodDesc);
Copy link
Member

Choose a reason for hiding this comment

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

I know we have IsLCGMethod on the native MethodDesc, but is LCG the term we want to keep using going forwards? Would IsReflectionEmitMethod make more sense?

At least personally, LCG is not a term I use - I just think of it as reflection emit.

Copy link
Member

Choose a reason for hiding this comment

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

DynamicMethod is the public name of the LCG feature.

ReflectionEmit is more general. Some methods emitted using Reflection.Emit are dynamic methods, some are not.

Copy link
Member

Choose a reason for hiding this comment

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

So then would this make sense:

  • IsDynamicMethod -> IsDynamicallyGeneratedMethod
  • IsLCGMethod -> IsDynamicMethod

Copy link
Member Author

Choose a reason for hiding this comment

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

... whereas I think of Reflection.Emit as a thing that only applies to things that generate the full metadata and types which is also not completely accurate... The actual term in the BCL is that an LCGMethod maps to a method represented by the System.Reflection.Emit.DynamicMethod class, but calling it a DynamicMethod... might be extra special confusing, since the runtime has multiple meanings of what a DynamicMethodDesc represents, only 1 of which is a System.Reflection.Emit.DynamicMethod. (And of course LCG stands for Lightweight CodeGen, which is an internal codename that probably doesn't still need to be kept around.) I'd like to hear what @lambdageek, and @AaronRobinsonMSFT think on this, as I know I'm not objective here as I've worked on this stuff for FAR too long to see things reasonably. Possibly the right approach would be to call it DynamicMethod and sometime in .NET 10, rename the DynamicMethodDesc to something like RuntimeILMethodDesc.

Copy link
Member

@AaronRobinsonMSFT AaronRobinsonMSFT Aug 10, 2024

Choose a reason for hiding this comment

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

I like the rename that elinor mentioned above at #106169 (comment).

rename the DynamicMethodDesc to something like RuntimeILMethodDesc.

I also agree with your suggestion above. I could also see GeneratedMethodDesc or ILEmitMethodDesc as options too.

Copy link
Member

Choose a reason for hiding this comment

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

I like IsLCGMethod -> IsDynamicMethod and DynamicMethodDesc ->ILEmitMethodDesc.

Not sure about IsDynamicMethod -> IsDynamicallyGeneratedMethod. Would just IsGeneratedILMethod be too broad?

Copy link
Member

Choose a reason for hiding this comment

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

DynamicMethodDesc ->ILEmitMethodDesc.

How about DynamicMethodDesc ->NoMetadataMethodDesc and IsDynamicMethod -> IsNoMetadata (existing property)?

Part of the codebase uses the NoMetadata name already:

inline DWORD IsNoMetadata() const
{
LIMITED_METHOD_DAC_CONTRACT;
return (mcDynamic == GetClassification());
}
. Notice that MethodDesc::IsDynamicMethod and MethodDesc::IsNoMetadata have the same implementation.

The key property of the DynamicMethodDesc is that it has no ECMA-335 metadata, it has no metadata token, etc.

Dynamic IL generation is not the key property of DynamicMethodDesc. The regular ECMA-335 MethodDescs can have dynamically generated (IL) code too. For example, UnsafeAccessors have dynamically generated IL but they are regular MethodDescs.

Copy link
Member Author

@davidwrighton davidwrighton Aug 12, 2024

Choose a reason for hiding this comment

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

I like @jkotas's idea here. Then the IsDynamicMethod api at the cdac level makes total sense, the existing DynamicMethodDesc translates to NoMetadataMethodDesc and gets a better name that more matches its utility, and we get rid of having both MethodDesc::IsNoMetadata and MethodDesc::IsDynamicMethod which are today exactly the same thing. While I'm at it, I'll rename mcDynamic to mcNoMetadata

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm trending toward the idea of putting this rename in a separate PR. Opinions?

Copy link
Member Author

Choose a reason for hiding this comment

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

Offline discussion is to put together a separate PR for that. I'll just add a commit to update the cdac contract side naming to IsDynamicMethod, and the renaming of the underlying structures to be less confusing will be a separate PR that will probably miss .NET 9.


// Return true if a MethodDesc represents an IL Stub dynamically generated by the runtime
public virtual bool IsILStub(MethodDescHandle methodDesc);
}
```

Expand Down Expand Up @@ -563,7 +600,8 @@ The version 1 `MethodDesc` APIs depend on the `MethodDescAlignment` global and t

| Global name | Meaning |
| --- | --- |
| `MethodDescAlignment` | `MethodDescChunk` trailing data is allocated in multiples of this constant. The size (in bytes) of each `MethodDesc` (or subclass) instance is a multiple of this constant.
| `MethodDescAlignment` | `MethodDescChunk` trailing data is allocated in multiples of this constant. The size (in bytes) of each `MethodDesc` (or subclass) instance is a multiple of this constant. |
| `MethodDescTokenRemainderBitCount` | Number of bits in the token remainder in `MethodDesc` |


In the runtime a `MethodDesc` implicitly belongs to a single `MethodDescChunk` and some common data is shared between method descriptors that belong to the same chunk. A single method table
Expand All @@ -572,12 +610,211 @@ will typically have multiple chunks. There are subkinds of MethodDescs at runti
We depend on the following data descriptors:
| Data Descriptor Name | Field | Meaning |
| --- | --- | --- |
| `MethodDesc` | `ChunkIndex` | Offset of this `MethodDesc` relative to the end of its containing `MethodDescChunk` - in multiples of `MethodDescAlignment`
| `MethodDesc` | `Slot` | The method's slot
| `MethodDesc` | `Flags` | The method's flags
| `MethodDescChunk` | `MethodTable` | The method table set of methods belongs to
| `MethodDescChunk` | `Next` | The next chunk of methods
| `MethodDescChunk` | `Size` | The size of this `MethodDescChunk` following this `MethodDescChunk` header, minus 1. In multiples of `MethodDescAlignment`
| `MethodDescChunk` | `Count` | The number of `MethodDesc` entries in this chunk, minus 1.
| `MethodDesc` | `ChunkIndex` | Offset of this `MethodDesc` relative to the end of its containing `MethodDescChunk` - in multiples of `MethodDescAlignment` |
| `MethodDesc` | `Slot` | The method's slot |
| `MethodDesc` | `Flags` | The method's flags |
| `MethodDesc` | `Flags3AndTokenRemainder` | More flags for the method, and the low bits of the method's token's RID |
| `MethodDescChunk` | `MethodTable` | The method table set of methods belongs to |
| `MethodDescChunk` | `Next` | The next chunk of methods |
| `MethodDescChunk` | `Size` | The size of this `MethodDescChunk` following this `MethodDescChunk` header, minus 1. In multiples of `MethodDescAlignment` |
| `MethodDescChunk` | `Count` | The number of `MethodDesc` entries in this chunk, minus 1. |
| `MethodDescChunk` | `FlagsAndTokenRange` | `MethodDescChunk` flags, and the upper bits of the method token's RID |
| `InstantiatedMethodDesc` | `PerInstInfo` | The pointer to the method's type arguments |
| `InstantiatedMethodDesc` | `Flags2` | Flags for the `InstantiatedMethodDesc` |
| `InstantiatedMethodDesc` | `NumGenericArgs` | How many generic args the method has |
| `StoredSigMethodDesc` | `Sig` | Pointer to a metadata signature |
| `StoredSigMethodDesc` | `cSig` | Count of bytes in the metadata signature |
| `StoredSigMethodDesc` | `ExtendedFlags` | Flags field for the `StoredSigMethodDesc` |
| `DynamicMethodDesc` | `MethodName` | Pointer to Null-terminated UTF8 string describing the Method desc |


And the following enumeration definitions

```csharp
internal enum MethodDescClassification
{
IL = 0, // IL
FCall = 1, // FCall (also includes tlbimped ctor, Delegate ctor)
PInvoke = 2, // PInvoke method
EEImpl = 3, // special method; implementation provided by EE (like Delegate Invoke)
Array = 4, // Array ECall
Instantiated = 5, // Instantiated generic methods, including descriptors
// for both shared and unshared code (see InstantiatedMethodDesc)
ComInterop = 6,
Dynamic = 7, // for method desc with no metadata behind
}

[Flags]
internal enum MethodDescFlags : ushort
{
ClassificationMask = 0x7,
HasNonVtableSlot = 0x0008,
}

internal enum InstantiatedMethodDescFlags2 : ushort
{
KindMask = 0x07,
GenericMethodDefinition = 0x01,
UnsharedMethodInstantiation = 0x02,
SharedMethodInstantiation = 0x03,
WrapperStubWithInstantiations = 0x04,
}

[Flags]
internal enum DynamicMethodDescExtendedFlags : uint
{
IsLCGMethod = 0x00004000,
IsILStub = 0x00008000,
}
```


And the various apis are implemented with the following algorithms

```csharp
public bool IsGenericMethodDefinition(MethodDescHandle methodDescHandle)
{
MethodDesc methodDesc = _methodDescs[methodDescHandle.Address];

if (methodDesc.Classification != MethodDescClassification.Instantiated)
return false;

ushort Flags2 = // Read Flags2 field from InstantiatedMethodDesc contract using address methodDescHandle.Address

return ((int)Flags2 & (int)InstantiatedMethodDescFlags2.KindMask) == (int)InstantiatedMethodDescFlags2.GenericMethodDefinition;
}

public ReadOnlySpan<TypeHandle> GetGenericMethodInstantiation(MethodDescHandle methodDescHandle)
{
MethodDesc methodDesc = _methodDescs[methodDescHandle.Address];

if (methodDesc.Classification != MethodDescClassification.Instantiated)
return default;

TargetPointer dictionaryPointer = // Read PerInstInfo field from InstantiatedMethodDesc contract using address methodDescHandle.Address
if (dictionaryPointer == 0)
return default;

int NumTypeArgs = // Read NumGenericArgs from methodDescHandle.Address using InstantiatedMethodDesc contract
TypeHandle[] instantiation = new TypeHandle[NumTypeArgs];
for (int i = 0; i < NumTypeArgs; i++)
instantiation[i] = GetTypeHandle(_target.ReadPointer(dictionaryPointer + _target.PointerSize * i));

return instantiation;
}

public uint GetMethodToken(MethodDescHandle methodDescHandle)
{
MethodDesc methodDesc = _methodDescs[methodDescHandle.Address];

TargetPointer methodDescChunk = // Using ChunkIndex from methodDesc, compute the wrapping MethodDescChunk

ushort Flags3AndTokenRemainder = // Read Flags3AndTokenRemainder field from MethodDesc contract using address methodDescHandle.Address

ushort FlagsAndTokenRange = // Read FlagsAndTokenRange field from MethodDescChunk contract using address methodDescChunk

int tokenRemainderBitCount = _target.ReadGlobal<byte>(Constants.Globals.MethodDescTokenRemainderBitCount);
int tokenRangeBitCount = 24 - tokenRemainderBitCount;
uint allRidBitsSet = 0xFFFFFF;
uint tokenRemainderMask = allRidBitsSet >> tokenRangeBitCount;
uint tokenRangeMask = allRidBitsSet >> tokenRemainderBitCount;

uint tokenRemainder = (uint)(_desc.Flags3AndTokenRemainder & tokenRemainderMask);
uint tokenRange = ((uint)(_chunk.FlagsAndTokenRange & tokenRangeMask)) << tokenRemainderBitCount;

return 0x06000000 | tokenRange | tokenRemainder;
}

public bool IsArrayMethod(MethodDescHandle methodDescHandle, out ArrayFunctionType functionType)
{
MethodDesc methodDesc = _methodDescs[methodDescHandle.Address];

if (methodDesc.Classification != MethodDescClassification.Array)
{
functionType = default;
return false;
}

int arrayMethodIndex = methodDesc.Slot - GetNumVtableSlots(GetTypeHandle(methodDesc.MethodTable));

functionType = arrayMethodIndex switch
{
0 => ArrayFunctionType.Get,
1 => ArrayFunctionType.Set,
2 => ArrayFunctionType.Address,
> 3 => ArrayFunctionType.Constructor,
_ => throw new InvalidOperationException()
};

return true;
}

public bool IsDynamicMethod(MethodDescHandle methodDescHandle, out ReadOnlySpan<byte> methodName)
{
MethodDesc methodDesc = _methodDescs[methodDescHandle.Address];

if (methodDesc.Classification != MethodDescClassification.Dynamic)
{
methodName = default;
return false;
}

TargetPointer methodNamePointer = // Read MethodName field from DynamicMethodDesc contract using address methodDescHandle.Address

methodName = // ReadBuffer from target of a utf8 null terminated string, starting at address methodNamePointer
return true;
}

public bool IsStoredSigMethodDesc(MethodDescHandle methodDescHandle, out ReadOnlySpan<byte> signature)
{
MethodDesc methodDesc = _methodDescs[methodDescHandle.Address];

switch (methodDesc.Classification)
{
case MethodDescClassification.Dynamic:
case MethodDescClassification.EEImpl:
case MethodDescClassification.Array:
break; // These have stored sigs

default:
signature = default;
return false;
}

TargetPointer Sig = // Read Sig field from StoredSigMethodDesc contract using address methodDescHandle.Address
uint cSig = // Read cSig field from StoredSigMethodDesc contract using address methodDescHandle.Address

TargetPointer methodNamePointer = // Read S field from DynamicMethodDesc contract using address methodDescHandle.Address
signature = // Read buffer from target memory starting at address Sig, with cSig bytes in it.
return true;
}

public bool IsLCGMethod(MethodDescHandle methodDescHandle)
{
MethodDesc methodDesc = _methodDescs[methodDescHandle.Address];

if (methodDesc.Classification != MethodDescClassification.Dynamic)
{
return false;
}

uint ExtendedFlags = // Read ExtendedFlags field from StoredSigMethodDesc contract using address methodDescHandle.Address

return ((DynamicMethodDescExtendedFlags)ExtendedFlags).HasFlag(DynamicMethodDescExtendedFlags.IsLCGMethod);
}

public bool IsILStub(MethodDescHandle methodDescHandle)
{
MethodDesc methodDesc = _methodDescs[methodDescHandle.Address];

if (methodDesc.Classification != MethodDescClassification.Dynamic)
{
return false;
}

uint ExtendedFlags = // Read ExtendedFlags field from StoredSigMethodDesc contract using address methodDescHandle.Address

return ((DynamicMethodDescExtendedFlags)ExtendedFlags).HasFlag(DynamicMethodDescExtendedFlags.IsILStub);
}
```
**TODO(cdac)**
13 changes: 13 additions & 0 deletions src/coreclr/debug/daccess/dacfn.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1386,6 +1386,15 @@ DacAllocHostOnlyInstance(ULONG32 size, bool throwEx)
return inst + 1;
}

thread_local bool t_DacAssertsUnconditionally = false;

bool DacSetEnableDacAssertsUnconditionally(bool enable)
{
bool oldValue = t_DacAssertsUnconditionally;
t_DacAssertsUnconditionally = enable;
return oldValue;
}

//
// Queries whether ASSERTs should be raised when inconsistencies in the target are detected
//
Expand All @@ -1404,6 +1413,10 @@ bool DacTargetConsistencyAssertsEnabled()
return true;
}

// If asserts are unconditionally enabled via the thread local, simply return true.
if (t_DacAssertsUnconditionally)
return true;

return g_dacImpl->TargetConsistencyAssertsEnabled();
}

Expand Down
1 change: 1 addition & 0 deletions src/coreclr/debug/daccess/dacimpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -1245,6 +1245,7 @@ class ClrDataAccess
HRESULT GetObjectStringDataImpl(CLRDATA_ADDRESS obj, unsigned int count, _Inout_updates_z_(count) WCHAR *stringData, unsigned int *pNeeded);
HRESULT GetUsefulGlobalsImpl(struct DacpUsefulGlobalsData *globalsData);
HRESULT GetMethodDescDataImpl(CLRDATA_ADDRESS methodDesc, CLRDATA_ADDRESS ip, struct DacpMethodDescData *data, ULONG cRevertedRejitVersions, DacpReJitData * rgRevertedRejitData, ULONG * pcNeededRevertedRejitData);
HRESULT GetMethodDescNameImpl(CLRDATA_ADDRESS methodDesc, unsigned int count, _Inout_updates_z_(count) WCHAR *name, unsigned int *pNeeded);

BOOL IsExceptionFromManagedCode(EXCEPTION_RECORD * pExceptionRecord);
#ifndef TARGET_UNIX
Expand Down
Loading