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

This contract describes the format of method, field, and local-variable signatures stored in target memory. Signatures use the ECMA-335 §II.23.2 format with CoreCLR-internal element types added by the runtime.

## Internal element types

The runtime extends the standard ECMA-335 element type encoding with values that may appear in signatures stored in target memory:

| Encoding | Value | Layout following the tag |
| --- | --- | --- |
| `ELEMENT_TYPE_INTERNAL` | `0x21` | a target-sized pointer to a runtime `TypeHandle` |
| `ELEMENT_TYPE_CMOD_INTERNAL` | `0x22` | one byte (`1` = required, `0` = optional), then a target-sized pointer to a runtime `TypeHandle` |

These tags are used in signatures generated internally by the runtime that are not persisted to a managed image. They are defined alongside the standard ECMA-335 element types in `src/coreclr/inc/corhdr.h`. Their literal values are part of this contract -- changing them is a breaking change.

## APIs of contract

```csharp
TypeHandle DecodeFieldSignature(BlobHandle blobHandle, ModuleHandle moduleHandle, TypeHandle ctx);
```

## Version 1

Data descriptors used:
| Data Descriptor Name | Field | Meaning |
| --- | --- | --- |
| _none_ | | |

Global variables used:
| Global Name | Type | Purpose |
| --- | --- | --- |
| _none_ | | |

Contracts used:
| Contract Name |
| --- |
| RuntimeTypeSystem |
| Loader |
| EcmaMetadata |

Constants:
| Constant Name | Meaning | Value |
| --- | --- | --- |
| `ELEMENT_TYPE_INTERNAL` | runtime-internal element type tag for an internal `TypeHandle` | `0x21` |
| `ELEMENT_TYPE_CMOD_INTERNAL` | runtime-internal element type tag for an internal modified type | `0x22` |

Decoding a signature follows the ECMA-335 §II.23.2 grammar. For all standard element types, decoding behaves identically to `System.Reflection.Metadata.SignatureDecoder<TType, TGenericContext>`. When the decoder encounters one of the runtime-internal tags above, it reads the target-sized pointer (and optional `required` byte for `ELEMENT_TYPE_CMOD_INTERNAL`) from the signature blob and resolves it to a runtime `TypeHandle`.

The decoder is implemented as `RuntimeSignatureDecoder<TType, TGenericContext>` -- a clone of SRM's `SignatureDecoder<TType, TGenericContext>` with added support for the runtime-internal element types. The clone takes an additional `Target` so internal-type pointers can be sized for the target architecture. Provider implementations implement `IRuntimeSignatureTypeProvider<TType, TGenericContext>` -- a superset of `System.Reflection.Metadata.ISignatureTypeProvider<TType, TGenericContext>` -- adding methods for the runtime-internal element types:

```csharp
TType GetInternalType(TargetPointer typeHandlePointer);
TType GetInternalModifiedType(TargetPointer typeHandlePointer, TType unmodifiedType, bool isRequired);
```

The contract's provider resolves these pointers through `RuntimeTypeSystem.GetTypeHandle`. Standard ECMA-335 element types resolve through `RuntimeTypeSystem.GetPrimitiveType` and `RuntimeTypeSystem.GetConstructedType`. Generic type parameters (`VAR`) and generic method parameters (`MVAR`) resolve via `RuntimeTypeSystem.GetInstantiation` and `RuntimeTypeSystem.GetGenericMethodInstantiation` respectively, using a `TypeHandle` (for generic types) or `MethodDescHandle` (for generic methods) generic context. `GetTypeFromDefinition` and `GetTypeFromReference` resolve tokens via the module's `TypeDefToMethodTableMap` / `TypeRefToMethodTableMap`; cross-module references and `GetTypeFromSpecification` are not currently implemented.

```csharp
TypeHandle ISignature.DecodeFieldSignature(BlobHandle blobHandle, ModuleHandle moduleHandle, TypeHandle ctx)
{
SignatureTypeProvider<TypeHandle> provider = new(_target, moduleHandle);
MetadataReader mdReader = _target.Contracts.EcmaMetadata.GetMetadata(moduleHandle)!;
BlobReader blobReader = mdReader.GetBlobReader(blobHandle);
RuntimeSignatureDecoder<TypeHandle, TypeHandle> decoder = new(provider, _target, mdReader, ctx);
return decoder.DecodeFieldSignature(ref blobReader);
}
```

### Other consumers

`RuntimeSignatureDecoder` is shared infrastructure within the cDAC. Other contracts construct their own decoder and provider directly when they need to decode method or local signatures rather than going through this contract. For example, the [StackWalk](./StackWalk.md) contract uses `RuntimeSignatureDecoder<GcTypeKind, GcSignatureContext>` with a GC-specific provider to classify method parameters during signature-based GC reference scanning.
70 changes: 0 additions & 70 deletions docs/design/datacontracts/SignatureDecoder.md

This file was deleted.

59 changes: 58 additions & 1 deletion docs/design/datacontracts/StackWalk.md
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,64 @@ At each frame yielded by `Filter`, the walk determines whether to scan for GC re
- **PrestubMethodFrame / CallCountingHelperFrame**: Use signature-based scanning.
- Other frame types: No GC roots to report.

See [GCRefMap Format and Resolution](#gcrefmap-format-and-resolution) for the GCRefMap scanning path details.
See [GCRefMap Format and Resolution](#gcrefmap-format-and-resolution) for the GCRefMap scanning path and [Signature-Based Scanning](#signature-based-scanning) for the signature decoding path.

### Signature-Based Scanning

When a transition frame's calling convention is not described by a precomputed GCRefMap (`PrestubMethodFrame`, `CallCountingHelperFrame`, and the fallback path for `StubDispatchFrame`/`ExternalMethodFrame`), the GC reference walk classifies caller-stack arguments by decoding the callee's method signature. This corresponds to native `TransitionFrame::PromoteCallerStack` (`src/coreclr/vm/frames.cpp`).

#### GcSignatureTypeProvider

`GcSignatureTypeProvider` is an `IRuntimeSignatureTypeProvider<GcTypeKind, GcSignatureContext>` that classifies each parameter type into one of:

```csharp
internal enum GcTypeKind
{
None, // Non-GC primitive that fits in a single slot
Ref, // Object reference (TYPE_GC_REF)
Interior, // Managed pointer / byref (TYPE_GC_BYREF)
Other, // Value type that may contain GC refs, or any type larger than a slot
}
```

The provider is scoped to the method's containing module (captured at construction) so that `TypeDef` and `TypeRef` tokens can be resolved to a loaded `MethodTable` via the module's `TypeDefToMethodTable` / `TypeRefToMethodTable` lookup tables. The decoder's generic context is a `GcSignatureContext(TypeHandle classContext, MethodDescHandle methodContext)` carrying the method's class and method instantiations.

The provider classifies primitives directly (`String`/`Object` -> `Ref`, `TypedReference` -> `Other`, others -> `None`). For `TypeDef`/`TypeRef` it resolves the loaded `TypeHandle` and classifies via `RuntimeTypeSystem.GetSignatureCorElementType`, treating enums (`IsEnum`) as their underlying primitive (`None`). When the type cannot be resolved (e.g., not yet loaded), classification falls back to the signature's `rawTypeKind` (`ValueType` -> `Other`, otherwise `Ref`). Arrays are `Ref`, byrefs are `Interior`, raw pointers are `None`. Generic parameters (`!T`, `!!T`) are resolved against the `GcSignatureContext` (via `GetInstantiation` / `GetGenericMethodInstantiation`) and classified by their actual instantiation -- matching native `SigTypeContext`-driven `PeekElemTypeNormalized` behavior. `ELEMENT_TYPE_INTERNAL` resolves the `TypeHandle` via `RuntimeTypeSystem.GetSignatureCorElementType` and maps the `CorElementType` to a `GcTypeKind`.

#### PromoteCallerStack Algorithm

1. Read the `MethodDesc` pointer from the `FramedMethodFrame` and obtain a `MethodDescHandle` from `RuntimeTypeSystem`.
2. Resolve the method's `MetadataReader` via `Loader.GetModuleHandleFromModulePtr` and `EcmaMetadata.GetMetadata`. If metadata is unavailable, no caller-stack refs are reported (matches native fallback behavior).
3. Obtain the method's signature blob, matching native `MethodDesc::GetSig`:
- If `RuntimeTypeSystem.IsStoredSigMethodDesc` is true (dynamic, EEImpl, and array method descs), pin the stored signature span and pass a `BlobReader` over it to `RuntimeSignatureDecoder.DecodeMethodSignature`.
- Otherwise, look up the signature via the metadata token (`mdMethodDef`), skipping methods with a nil token (`0x06000000`).
4. Decode the signature with `RuntimeSignatureDecoder<GcTypeKind, GcSignatureContext>` and a `GcSignatureTypeProvider` constructed for the method's module. The `GcSignatureContext` passes the method's class and method instantiations so that `VAR`/`MVAR` placeholders resolve to their actual types. See [Signature contract](./Signature.md) for the decoder.
5. Skip varargs methods (the caller-stack layout is not described by the callee signature alone).
6. Compute the number of reserved register slots in the `TransitionBlock`:

| Reserved Slot | Condition |
|---|---|
| `this` pointer | `MethodSignature.Header.IsInstance` |
| Return buffer | Return type is `GcTypeKind.Other` |
| Generic instantiation arg | `RuntimeTypeSystem.RequiresInstArg(methodDesc)` |
| Async continuation | `RuntimeTypeSystem.IsAsyncMethod(methodDesc)` |
| ARM64 indirect-result register (`x8`) | Target architecture is ARM64 |

7. If `IsInstance`, report the `this` slot at position `0` (or `1` on ARM64 to skip `x8`). The slot is reported as `GC_CALL_INTERIOR` for value-type `this`, otherwise as a normal reference.
Comment thread
max-charlamb marked this conversation as resolved.
8. Walk `MethodSignature.ParameterTypes` starting at slot index = reserved slot count, advancing one slot per parameter:
- `GcTypeKind.Ref` -> report as a reference.
- `GcTypeKind.Interior` -> report with `GC_CALL_INTERIOR`.
- `GcTypeKind.Other` / `GcTypeKind.None` -> not reported (large value types are reported via the GCRefMap path when one is available; otherwise their interior refs are not visible to this scan).

The slot address is computed using the same formula as the GCRefMap path:

```csharp
slotAddress = transitionBlockPtr + FirstGCRefMapSlot + (position * pointerSize);
```

#### Limitations vs. Native

This signature-based scan has known gaps relative to native see [dotnet/runtime#127765](https://github.com/dotnet/runtime/issues/127765) for tracking.

### GCRefMap Format and Resolution

Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/inc/corhdr.h
Original file line number Diff line number Diff line change
Expand Up @@ -913,7 +913,7 @@ typedef enum CorElementType
ELEMENT_TYPE_CMOD_OPT = 0x20, // optional C modifier : E_T_CMOD_OPT <mdTypeRef/mdTypeDef>

// This is for signatures generated internally (which will not be persisted in any way).
// [cDAC] [RuntimeTypeSystem]: Contract depends on the values of ELEMENT_TYPE_INTERNAL and ELEMENT_TYPE_CMOD_INTERNAL.
// [cDAC] [Signature][RuntimeTypeSystem]: Contract depends on the values of ELEMENT_TYPE_INTERNAL and ELEMENT_TYPE_CMOD_INTERNAL.
ELEMENT_TYPE_INTERNAL = 0x21, // INTERNAL <typehandle>
ELEMENT_TYPE_CMOD_INTERNAL = 0x22, // CMOD_INTERNAL <required (1 byte: non-zero if required, 0 if optional)> <typehandle>

Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/vm/datadescriptor/datadescriptor.inc
Original file line number Diff line number Diff line change
Expand Up @@ -1574,7 +1574,7 @@ CDAC_GLOBAL_CONTRACT(ReJIT, c1)
CDAC_GLOBAL_CONTRACT(RuntimeInfo, c1)
CDAC_GLOBAL_CONTRACT(RuntimeTypeSystem, c1)
CDAC_GLOBAL_CONTRACT(SHash, c1)
Comment thread
max-charlamb marked this conversation as resolved.
CDAC_GLOBAL_CONTRACT(SignatureDecoder, c1)
CDAC_GLOBAL_CONTRACT(Signature, c1)
CDAC_GLOBAL_CONTRACT(StackWalk, c1)
CDAC_GLOBAL_CONTRACT(StressLog, c2)
CDAC_GLOBAL_CONTRACT(SyncBlock, c1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,9 @@ public abstract class ContractRegistry
/// </summary>
public virtual ICodeNotifications CodeNotifications => GetContract<ICodeNotifications>();
/// <summary>
/// Gets an instance of the SignatureDecoder contract for the target.
/// Gets an instance of the Signature contract for the target.
/// </summary>
public virtual ISignatureDecoder SignatureDecoder => GetContract<ISignatureDecoder>();
public virtual ISignature Signature => GetContract<ISignature>();
/// <summary>
/// Gets an instance of the SyncBlock contract for the target.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@

namespace Microsoft.Diagnostics.DataContractReader.Contracts;

public interface ISignatureDecoder : IContract
public interface ISignature : IContract
{
static string IContract.Name { get; } = nameof(SignatureDecoder);
static string IContract.Name { get; } = nameof(Signature);
TypeHandle DecodeFieldSignature(BlobHandle blobHandle, ModuleHandle moduleHandle, TypeHandle ctx) => throw new NotImplementedException();
}

public readonly struct SignatureDecoder : ISignatureDecoder
public readonly struct Signature : ISignature
{
// Everything throws NotImplementedException
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Reflection.Metadata;

namespace Microsoft.Diagnostics.DataContractReader.SignatureHelpers;

/// <summary>
/// Superset of SRM's <see cref="ISignatureTypeProvider{TType, TGenericContext}"/>
/// that adds support for runtime-internal type codes
/// (<c>ELEMENT_TYPE_INTERNAL</c> 0x21 and <c>ELEMENT_TYPE_CMOD_INTERNAL</c> 0x22).
/// </summary>
/// <remarks>
/// Providers implementing this interface automatically satisfy SRM's
/// <see cref="ISignatureTypeProvider{TType, TGenericContext}"/> and can be used
/// with both SRM's <c>SignatureDecoder</c> and our
/// <see cref="RuntimeSignatureDecoder{TType, TGenericContext}"/>.
/// </remarks>
public interface IRuntimeSignatureTypeProvider<TType, TGenericContext>
: ISignatureTypeProvider<TType, TGenericContext>
{
/// <summary>
/// Classify an <c>ELEMENT_TYPE_INTERNAL</c> (0x21) type by resolving the
/// embedded TypeHandle pointer via the target's runtime type system.
/// </summary>
TType GetInternalType(TargetPointer typeHandlePointer);

/// <summary>
/// Classify an <c>ELEMENT_TYPE_CMOD_INTERNAL</c> (0x22) custom modifier by
/// resolving the embedded TypeHandle pointer via the target's runtime type system.
/// </summary>
TType GetInternalModifiedType(TargetPointer typeHandlePointer, TType unmodifiedType, bool isRequired);
}
Loading
Loading