-
Notifications
You must be signed in to change notification settings - Fork 5.4k
[cDAC] RuntimeSignatureDecoder and centralized Signature contract (2/5) #127636
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
max-charlamb
wants to merge
9
commits into
dotnet:main
Choose a base branch
from
max-charlamb:cdac-stackrefs-pr2
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
6083571
WIP: RuntimeSignatureDecoder, centralized SignatureDecoder contract
c468dde
Align RuntimeSignatureDecoder with SRM and move GC decoding to StackWalk
112b4be
Address review feedback: split interface, drop dead code, require Met…
d5e314a
Capture Target in providers; document signature-based GC scanning
bfaf8af
Address PR review: handle StoredSigMethodDesc; cache GcSignatureTypeP…
d70e706
Resolve VAR/MVAR via instantiation; normalize enums to underlying pri…
ba37f41
Address PR review: visibility, dead state, doc fixes
b8179d0
Rename SignatureDecoder contract to Signature
e8f72c1
Address review feedback: data-first Signature.md + cross-ref tag
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
| @@ -0,0 +1,73 @@ | ||||
| # 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 two CoreCLR-internal element types added by the runtime. | ||||
|
|
||||
| ## Internal element types | ||||
|
|
||||
| The runtime extends the standard ECMA-335 element type encoding with two 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. | ||||
|
|
||||
| Tag `3` in the `TypeDefOrRefOrSpec` encoding (ECMA-335 §II.23.2.8) is reserved and decoders throw `BadImageFormatException` when they encounter it. | ||||
|
|
||||
|
Comment on lines
+16
to
+17
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
This is duplicating ECMA-335 §II.23.2 |
||||
| ## 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 two 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 two 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 two 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. | ||||
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -471,7 +471,69 @@ 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. | ||
| 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 is conservative compared to native: | ||
|
|
||
| * It does not enumerate embedded GC refs inside large value types passed by value (a `GcTypeKind.Other` parameter is silently skipped). | ||
| * It does not yet apply native's `ArgIterator`-driven multi-slot / HFA layout, nor does it model `String` constructors or `SuppressParamTypeArg`. | ||
|
|
||
| These limitations are visible to the cDAC GC stress verification harness, which compares cDAC and native walks; they may be tightened in future versions of this contract. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we track these TODOs as issues? |
||
|
|
||
| ### GCRefMap Format and Resolution | ||
|
|
||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
33 changes: 33 additions & 0 deletions
33
...nostics.DataContractReader.Contracts/Contracts/Signature/IRuntimeSignatureTypeProvider.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Chances are we are going to add a third one. Avoid hardcoding "two" in the text to make future proof.