-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Design to support ByRefLike types in Generics #67129
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
Merged
stephentoub
merged 14 commits into
dotnet:main
from
AaronRobinsonMSFT:generic_byreflike_design
Nov 4, 2022
Merged
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
ebd8c28
Design to support ByRefLike types in Generics
AaronRobinsonMSFT a927231
Trim whitespace
AaronRobinsonMSFT affe2c1
Review feedback
AaronRobinsonMSFT 9d17a02
Remove words
AaronRobinsonMSFT 9cb2a57
Update based on feedback
AaronRobinsonMSFT f292968
Add F# as being impacted.
AaronRobinsonMSFT 2cb76f2
Feedback and clarify wording for constrained calls.
AaronRobinsonMSFT 489f684
Add well-known IL sequences for boxing
AaronRobinsonMSFT 9a66ba1
Minor nits.
AaronRobinsonMSFT 0468624
Update docs/design/features/byreflike-generics.md
AaronRobinsonMSFT 7044a94
Review feedback
AaronRobinsonMSFT 8eaf856
Update to design doc
AaronRobinsonMSFT 75fd91b
Misspelling
AaronRobinsonMSFT 4658207
Update byreflike-generics.md
AaronRobinsonMSFT 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,129 @@ | ||
| # Generics parameters of ByRefLike types | ||
|
|
||
| Using ByRefLike types in Generic parameters is possible by building upon support added for `ref` fields. Scenarios that would benefit most from this are those involving `Span<T>`. For example, consider the following examples: | ||
|
|
||
| - `Span<TypedReference>` – Represents the general case where a ByRefLike type is used as a Generic parameter. This specific case would be desirable for a more efficient Reflection API. | ||
| - `Span<Span<char>>` – Nested `Span<T>` types would be of benefit in the parsing result of strings. | ||
|
|
||
| ## Runtime impact | ||
AaronRobinsonMSFT marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| Supporting ByRefLike type as Generic parameters will impact the following IL instructions: | ||
AaronRobinsonMSFT marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| - `box` – Types with ByRefLike parameters used in fields cannot be boxed. | ||
AaronRobinsonMSFT marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| - `stsfld` / `ldsfld` – Type fields of a ByRefLike parameter cannot be marked `static`. | ||
AaronRobinsonMSFT marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| - `newarr` / `stelem` / `ldelem` / `ldelema` – Arrays are not able to contain ByRefLike types. | ||
| - `newobj` – For multi-dimensional array construction. | ||
| - `constrained.callvirt` – If this IL sequence resolves to a method implemented on `object` or default interface method, an error will occur during the attempt to box the instance. | ||
|
|
||
| If any of the above instructions are attempted to be used with a ByRefLike type, the runtime will throw an `InvalidProgramException`. Sequences involving some of the above instructions are considered optimizations and represent cases that will remain valid regardless of a `T` being ByRefLike. See "Special IL Sequences" section below for details. | ||
|
|
||
| The following instructions are already set up to support this feature since their behavior will fail as currently defined due to the inability to box a ByRefLike type. | ||
|
|
||
| - `throw` – Requires an object reference to be on stack, which can never be a ByRefLike type. | ||
| - `unbox` / `unbox.any` – Requires an object reference to be on stack, which can never be a ByRefLike type. | ||
AaronRobinsonMSFT marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| - `isinst` – Will always place `null` on stack. | ||
AaronRobinsonMSFT marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| - `castclass` – Will always throw `InvalidCastException`. | ||
|
|
||
| The expansion of ByRefLike types as Generic parameters does not relax restrictions on where ByRefLike types can be used. When `T` is ByRefLike, the use of `T` as a field will require the enclosing type to be ByRefLike. | ||
|
|
||
| ## API Proposal | ||
|
|
||
| Support for the following will be indicated by a new property. For .NET 7, the feature will be marked with `RequiresPreviewFeaturesAttribute` to indicate it is in [preview](https://github.com/dotnet/designs/blob/main/accepted/2021/preview-features/preview-features.md). | ||
|
|
||
| ```diff | ||
| namespace System.Runtime.CompilerServices | ||
| { | ||
| public static partial class RuntimeFeature | ||
| { | ||
| + /// <summary> | ||
| + /// Represents a runtime feature where byref-like types can be used in Generic parameters. | ||
| + /// </summary> | ||
| + public const string GenericsAcceptByRefLike = nameof(GenericsAcceptByRefLike); | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| The compiler will need an indication for existing troublesome APIs where ByRefLike types will be permissable, but where the failure will be handled at runtime. An attribute will be created and added to these APIs. | ||
|
|
||
| ```csharp | ||
| namespace System.Runtime.CompilerServices | ||
| { | ||
| /// <summary> | ||
| /// Indicates to the compiler that constraint checks should be suppressed | ||
| /// and will instead be enforced at run-time. | ||
| /// </summary> | ||
AaronRobinsonMSFT marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| [AttributeUsage(AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property)] | ||
| internal sealed class SuppressConstraintChecksAttribute : Attribute | ||
| { } | ||
| } | ||
| ``` | ||
|
|
||
| Troublesome APIs: | ||
AaronRobinsonMSFT marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| - [`Span<T>`](https://docs.microsoft.com/dotnet/api/system.span-1) | ||
| - `public Span(T[]? array);` | ||
AaronRobinsonMSFT marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| - `public Span(T[]? array, int start, int length);` | ||
| - `public T[] ToArray();` | ||
| - `public static implicit operator Span<T>(ArraySegment<T> segment);` | ||
| - `public static implicit operator Span<T>(T[]? array);` | ||
| - [`ReadOnlySpan<T>`](https://docs.microsoft.com/dotnet/api/system.readonlyspan-1) | ||
| - `public ReadOnlySpan(T[]? array);` | ||
| - `public ReadOnlySpan(T[]? array, int start, int length);` | ||
| - `public T[] ToArray();` | ||
| - `public static implicit operator ReadOnlySpan<T>(ArraySegment<T> segment);` | ||
| - `public static implicit operator ReadOnlySpan<T>(T[]? array);` | ||
|
|
||
| A new `GenericParameterAttributes` value will be defined which also represents metadata defined in the `CorGenericParamAttr` enumeration. | ||
|
|
||
AaronRobinsonMSFT marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ```diff | ||
| namespace System.Reflection | ||
| { | ||
| [Flags] | ||
| public enum GenericParameterAttributes | ||
| { | ||
| + AcceptByRefLike = 0x0020 | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ```diff | ||
| typedef enum CorGenericParamAttr | ||
| { | ||
| + gpAcceptByRefLike = 0x0020 // type argument can be ByRefLike | ||
| } CorGenericParamAttr; | ||
| ``` | ||
|
|
||
AaronRobinsonMSFT marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| The expansion of metadata will impact at least the following: | ||
|
|
||
| - ILDasm/ILAsm/`System.Reflection.Metadata`/`System.Reflection.Emit` – https://github.com/dotnet/runtime | ||
| - Cecil – https://github.com/jbevain/cecil | ||
| - IL Trimmer – https://github.com/dotnet/linker | ||
| - F# – https://github.com/fsharp/fsharp | ||
| - C++/CLI – The MSVC team | ||
|
|
||
| ## Semantic Proposal | ||
|
|
||
| An API that is a JIT-time intrinsic will be needed to determine if a parameter is ByRefLike. This API would represent a check to occur at JIT time to avoid taking paths that would be invalid for some values of `T`. The existing `Type.IsByRefLike` property will be made an intrinsic (e.g., `typeof(T).IsByRefLike`). | ||
|
|
||
| For dispatch to object implemented methods and to default interface methods, the behavior shall be that an `InvalidProgramException` should be thrown. The JIT will insert the following IL at code-gen time. | ||
|
|
||
| ``` | ||
| newobj instance void System.InvalidProgramException::.ctor() | ||
| throw | ||
| ``` | ||
|
|
||
| Adding `gpAcceptByRefLike` to the metadata of a Generic parameter will be considered a non-breaking binary change. | ||
|
|
||
| Enumerating of constructors/methods on `Span<T>` and `ReadOnlySpan<T>` may throw `TypeLoadException` if `T` is a ByRefLike type. See "Troublesome APIs" above for the list of APIs that cause this condition. | ||
|
|
||
| ## Special IL Sequences | ||
|
|
||
| The following are IL sequences involving the `box` instruction. They are used for common C# language constructs and shall continue to be valid, even with ByRefLike types, in cases where the result can be computed at JIT time and elided safely. These sequences must now be elided when the target type is ByRefLike. The conditions where each sequence is elided are described below and each condition will be added to the ECMA-335 addendum. | ||
|
|
||
| `box` ; `unbox.any` – The box target type is equal to the unboxed target type. | ||
|
|
||
| `box` ; `br_true/false` – The box target type is non-`Nullable<T>`. | ||
|
|
||
| `box` ; `isinst` ; `unbox.any` – The box, `isint`, and unbox target types are all equal. | ||
|
|
||
| `box` ; `isinst` ; `br_true/false` – The box target type is equal to the unboxed target type or the box target type is `Nullable<T>` and target type equalities can be computed. | ||
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.
Uh oh!
There was an error while loading. Please reload this page.