-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
Implement Generic support for UnsafeAccessorAttribute
#89439
Comments
Tagging subscribers to this area: @dotnet/area-system-runtime-compilerservices Issue DetailsOriginally defined in #81741, Generic support in
We are waiting to see community need for this work so setting to Future.
|
@lambdageek @MichalStrehovsky @jkotas @fanyang-mono This issue is being used to track Generic support going forward. |
This would be really useful when working with generic code for example in Serialization scenarios. Without this a lot of things still require DynamicMethod. |
Is it worth clarifying that specific generic instances, work already. So if we have [UnsafeAccessor(UnsafeAccessorKind.Field, Name="_myList")]
static extern ref List<string> StringField(MyClass<string> @this);
[UnsafeAccessor(UnsafeAccessorKind.Field, Name="_myList")]
static extern ref List<double> DoubleField(MyClass<double> @this); just not the fully generic [UnsafeAccessor(UnsafeAccessorKind.Field, Name="_myList")]
static extern ref List<T> Field<T>(MyClass<T> @this); |
UnsafeAccessorAttribute
UnsafeAccessorAttribute
Agreed. I've updated the title to state "unbound" generics. Will move your examples up to the main area. |
Just to express support for the feature, writing an efficient dictionary implementation that inherits from |
Just discovered that generic support hasn't been implemented yet (and, as it currently seems, won't be implemented until .NET 9) during the unit-testing stage of my library, and thank god I did :) This feature is definitely needed. In fact, I'd argue that it's somewhat counter-intuitive that it isn't functional yet. This is especially so given that it was mentioned in the original proposal, and that the general principle - "copy-paste the original signature, add the target type as the first parameter, and you're good to go" - fails in this instance without any compiler warning (I hope it does for NAOT, though I haven't tested it yet). In my specific situation, I attempted to access My problem here can be broken down into two components:
There's a suggestion to establish a "bridging" system for generic constraints (dotnet/csharplang#6308) that might address (2). However, since it's proposed as a language feature, and the consensus on its design is yet to be found, it may take another 5 or 10 years to implement. So, why not consider it as a runtime feature instead, while simultaneously addressing (1)? The original proposal states:
I suggest either entirely disregarding this section or, preferably, introducing a mechanism to deliberately opt out of this verification. For instance, in my scenario, if I wish to call [UnsafeAccessorIgnoreGenericConstraints]
[UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = nameof(Enum.TryFormat))]
static extern bool TryFormat<TEnum>(Enum? targetType, TEnum value, Span<char> destination, out int charsWritten, ReadOnlySpan<char> format); To be quite frankly honest with you, this approach seems so much better compared to relying on the existence of an internal helper method, as such methods can be moved, renamed, or deleted in any non-breaking .NET update, as they aren't part of the public API. If the final implementation of unsafe accessors evolves in this direction, it would be fantastic, as it simultaneously addresses:
At the end of the day, they are unsafe accessors, so incorporating this feature seems to fit seamlessly within their purview. In any case, I'm eager to hear others' opinions on this topic! :) |
I feel like adding such support would be useful in some cases, the runtime would just throw when the T doesn't meet the constraints, not when the constraints don't match. |
F# has a feature of generic state machine builders. It's a feature that empowers you to write all sorts of stuff like specialized These generally relay on template type that has among others has a It would be really nice to have an Just wanted to share a use case for this. |
We could also use this in CsWinRT (here), so we could go from this: private static Func<T, IObjectReference> BindToAbi()
{
var objField = HelperType.GetField("_obj", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
return (T arg) => (IObjectReference) objField.GetValue(arg);
} To this: private static Func<T, IObjectReference> BindToAbi()
{
[UnsafeAccessor(UnsafeAccessorKind.Field, Name="_obj")]
static extern ref IObjectReference GetObj(T _this);
return (T arg) => GetObj(arg);
} Or actually just skip the function entirely and just call |
[UnsafeAccessor(UnsafeAccessorKind.Field, Name="_obj")]
static extern ref IObjectReference GetObj(T _this); Field access in this manner on reference types won't work with shared generics, there are no fields on |
UnsafeAccessorAttribute
UnsafeAccessorAttribute
Is there some information available on which scenarios are going to be supported? I'm mostly interested in whether it is going to be possible to define an accessor in a generic class and if generic methods are going to work. At the moment both of these examples result in 'Invalid usage of UnsafeAccessorAttribute'. // note no generics are used in the accessor itself
public class Accessors<T>
{
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "value")]
public static extern ref int GetValue(MyClass _this);
} [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "Method")]
public static extern T Method<T>(MyClass _this); If the answer to the above is yes, is a more complicated example like the following going to work? public class Outer<TOuter>
{
public class Inner<TInner>
{
Tuple<TOuter, TInner, T>? Method<T>() => null;
}
}
public class Accessors<TOuter, TInner>
{
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "Method")]
public static extern Tuple<TOuter, TInner, T> Method<T>(Outer<TOuter>.Inner<TInner> _this);
} or at least this? public class Accessors
{
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "Method")]
public static extern Tuple<TOuter, TInner, T> Method<TOuter, TInner, T>(Outer<TOuter>.Inner<TInner> _this);
} |
Since static members don't need an instance, could this accept a System.Type instead? [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "get_Descriptor")]
public extern static MessageDescriptor GetDescriptor(System.Type type); |
No. The design requires the type to be explicit. As mentioned though, since the target member is Using |
Hi, Did these changes make it into 9.0.100-preview.3.24204.13? I am trying for example to do this: public static class SortedListAccessor<TKey, TValue> where TKey : notnull
{
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "keys")]
public static extern ref TKey[] GetKeys(SortedList<TKey, TValue> list);
}
var l = new SortedList<string, string>();
var keys = SortedListAccessor<string, string>.GetKeys(l); But I get a |
No, it'll be available in Preview 4 |
1. Adds support for compiling generic wrapper methods to Mono. In some situations we need to emit a wrapper method that is generic. This makes the MethodBuilder infrastructure support that. 2. Adds support for inflating generic wrapper data. Wrappers have pointer data associated with them that is used by the code generator (For example instead of emitting field tokens, we record the `MonoClassField*` directly and then emit a fake token that is just the index of the `MonoClassField*` in the `MonoMethodWrapper:method_data` array). The pointer data associated with a wrapper is normally just consumed verbatim. But if the wrapper is part of a generic class, or if the wrapper is a generic method, the wrapper data might have generic parameters (for example it might be a MonoClassField for `MyList<T>` instead of `MyList<string>`). Add support for tagging the data with its kind and inflating it if the wrapper method is inflated. 3. Applies (1) and (2) to unsafe accessor methods - the unsafe accesor wrapper generation now always tries to get the generic definition and to generate a wrapper for that generic definition and then inflate it. 4. Some AOT changes so that FullAOT substitutes lookups for an unsafe accessor by lookups for the wrapper. Including if the unsafe accessor or wrapper is generic. This also enabled gshared and gsharedvt for unsafe accessor wrappers. This also fixes #92883 Contributes to #99830, #89439 **NOT DONE** - We don't check constraints on the generic target types yet --- * always AOT wrappers, even for generics, not the actual accessor * add generic wrapper methods * use generic method owner caches * lookup unsafe accessor wrapper instances in aot-runtime if someone needs the unsafe accessor, lookup the wrapper instead. Needed when there's a call for extra instances * cleanup marshaling - dont' use ctx as a flag * handle some generic field accessors As long as the target is just some type that mentions a generic field, we're ok - the regular gsharing ldflda works. It just can't be a type variable. * issues.targets: enable some unsafe accessor AOT tests * [metadata] add ability to inflate wrapper data When we create generic wrappers (or wrappers in a generic class), if the wrapper data needs to refer to a field, method, or parameter type of the definition, that data might need to be inflated if the containing class is inflated (or the generic wrapper method is inflated). Add a new function to opt into inflation: ```c get_marshal_cb ()->mb_inflate_wrapper_data (mb); ``` Add a new function to be called after mono_mb_emit_op (or any other call that calls mono_mb_add_data): ```c mono_mb_emit_op (mb, CEE_LDFLDA, field); mono_mb_set_wrapper_data_kind (mb, MONO_MB_ILGEN_WRAPPER_DATA_FIELD); ``` Note: mono_mb_set_wrapper_data_kind asserts if you haven't called mb_inflate_wrapper_data. TODO: add more wrapper data kinds for MonoMethod* and MonoClass* etc * [marshal] refactor unsafe accessor; opt into inflate_wrapper_data Try to separate the ideas of "the call to the UnsafeAccessor method was inflated, so we need to inflate the wrapper" and "the UnsafeAccessor method is a generic method definition, so the wrapper should be a generic method definition, too" * inflate MonoMethod wrapper data; impl ctor generic unsafe accessors * fix windows build * [aot] handle case of partial generic sharing for unsafe accessor In partial generic sharing, a call to an instance like `Foo<int>` is replaced by `Foo<T_INT>` where T is constrained to `int` and enums that use `int` as a base type. In that case the AOT compiler will emit the unsafe accessor wrapper instantiated with `T_INT`. So in the AOT lookup we have to find calls to `UnsafeAccessor<int>` and replace them with calls to `(wrapper) UnsafeAccessor<T_INT>` to match what the AOT compiler is doing * [aot] for unsafe accessor wrappers with no name, record a length 0 This is needed because for inflated unsafe accessors we write the inflated bit after the name. So if we're accessing a constructor and we didn't record a name in the AOT image, we would erroneously read the inflated bit as the name length. * [aot-runtime] try to fix gsharedvt lookup * better comments; remove fied FIXMEs * update HelloWorld sample to support either normal AOT or FullAOT * rename helper methods * apply suggestions from code review * DRY. compute inflate_generic_data in one place * Just do one loop for inflating generic wrapper data * better comments * DRY. move common AOT code to mini.c
1. Adds support for compiling generic wrapper methods to Mono. In some situations we need to emit a wrapper method that is generic. This makes the MethodBuilder infrastructure support that. 2. Adds support for inflating generic wrapper data. Wrappers have pointer data associated with them that is used by the code generator (For example instead of emitting field tokens, we record the `MonoClassField*` directly and then emit a fake token that is just the index of the `MonoClassField*` in the `MonoMethodWrapper:method_data` array). The pointer data associated with a wrapper is normally just consumed verbatim. But if the wrapper is part of a generic class, or if the wrapper is a generic method, the wrapper data might have generic parameters (for example it might be a MonoClassField for `MyList<T>` instead of `MyList<string>`). Add support for tagging the data with its kind and inflating it if the wrapper method is inflated. 3. Applies (1) and (2) to unsafe accessor methods - the unsafe accesor wrapper generation now always tries to get the generic definition and to generate a wrapper for that generic definition and then inflate it. 4. Some AOT changes so that FullAOT substitutes lookups for an unsafe accessor by lookups for the wrapper. Including if the unsafe accessor or wrapper is generic. This also enabled gshared and gsharedvt for unsafe accessor wrappers. This also fixes dotnet#92883 Contributes to dotnet#99830, dotnet#89439 **NOT DONE** - We don't check constraints on the generic target types yet --- * always AOT wrappers, even for generics, not the actual accessor * add generic wrapper methods * use generic method owner caches * lookup unsafe accessor wrapper instances in aot-runtime if someone needs the unsafe accessor, lookup the wrapper instead. Needed when there's a call for extra instances * cleanup marshaling - dont' use ctx as a flag * handle some generic field accessors As long as the target is just some type that mentions a generic field, we're ok - the regular gsharing ldflda works. It just can't be a type variable. * issues.targets: enable some unsafe accessor AOT tests * [metadata] add ability to inflate wrapper data When we create generic wrappers (or wrappers in a generic class), if the wrapper data needs to refer to a field, method, or parameter type of the definition, that data might need to be inflated if the containing class is inflated (or the generic wrapper method is inflated). Add a new function to opt into inflation: ```c get_marshal_cb ()->mb_inflate_wrapper_data (mb); ``` Add a new function to be called after mono_mb_emit_op (or any other call that calls mono_mb_add_data): ```c mono_mb_emit_op (mb, CEE_LDFLDA, field); mono_mb_set_wrapper_data_kind (mb, MONO_MB_ILGEN_WRAPPER_DATA_FIELD); ``` Note: mono_mb_set_wrapper_data_kind asserts if you haven't called mb_inflate_wrapper_data. TODO: add more wrapper data kinds for MonoMethod* and MonoClass* etc * [marshal] refactor unsafe accessor; opt into inflate_wrapper_data Try to separate the ideas of "the call to the UnsafeAccessor method was inflated, so we need to inflate the wrapper" and "the UnsafeAccessor method is a generic method definition, so the wrapper should be a generic method definition, too" * inflate MonoMethod wrapper data; impl ctor generic unsafe accessors * fix windows build * [aot] handle case of partial generic sharing for unsafe accessor In partial generic sharing, a call to an instance like `Foo<int>` is replaced by `Foo<T_INT>` where T is constrained to `int` and enums that use `int` as a base type. In that case the AOT compiler will emit the unsafe accessor wrapper instantiated with `T_INT`. So in the AOT lookup we have to find calls to `UnsafeAccessor<int>` and replace them with calls to `(wrapper) UnsafeAccessor<T_INT>` to match what the AOT compiler is doing * [aot] for unsafe accessor wrappers with no name, record a length 0 This is needed because for inflated unsafe accessors we write the inflated bit after the name. So if we're accessing a constructor and we didn't record a name in the AOT image, we would erroneously read the inflated bit as the name length. * [aot-runtime] try to fix gsharedvt lookup * better comments; remove fied FIXMEs * update HelloWorld sample to support either normal AOT or FullAOT * rename helper methods * apply suggestions from code review * DRY. compute inflate_generic_data in one place * Just do one loop for inflating generic wrapper data * better comments * DRY. move common AOT code to mini.c
Mono part of support is now complete. Closing this issue now, as all planed tasks have been completed. |
Originally defined in #81741, Generic support in
UnsafeAccessorAttribute
scenarios was not added. This issue tracks that effort across the runtimes.UnsafeAccessorAttribute
supports generic parameters #99468UnsafeAccessorAttribute
supports generic parameters #99468UnsafeAccessorAttribute
#99830UnsafeAccessorAttribute
- Add examples for generic support. dotnet-api-docs#9746For bound generic parameters this was originally thought to work but was broken after the final signature validation logic was implemented. This was reported in #92633 as a regression. That issue has been subsequently closed and folded into this work to support Generics on bound parameters too.
Example with unbound generic parameters.
The text was updated successfully, but these errors were encountered: