-
-
Notifications
You must be signed in to change notification settings - Fork 754
NativeAOT support #1998
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
NativeAOT support #1998
Conversation
|
This doesn't work today. Besides having warnings that remain, the various attributes that "allow" portions of the library to include dynamic code (either Ref.Emit or closing generic types at runtime) or that require unreferenced code is viral and bubbles up the most of the library. It's unclear whether a path to success exists. I have a meeting scheduled for next week with the .NET folks who help with such migrations and will report back on what I learn. |
| displayName: 🧪 dotnet test | ||
| condition: and(succeeded(), ${{ parameters.RunTests }}) | ||
|
|
||
| - script: dotnet publish -c Release -r ${{ parameters.osRID }}-x64 -warnaserror |
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.
You shouldn't need to pass -r (or -c for that matter, as Release is the default for publish). The RID is calculated based on the current machine.
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.
Dropping the -r switch produces a build error:
error NETSDK1191: A runtime identifier for the property 'PublishAot' couldn't be inferred. Specify a rid explicitly.
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.
Does that happen all the time? Or only for specific operating systems?
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.
It happened on Windows when I tested it locally. That's as far as I got.
| [UnconditionalSuppressMessage("ReflectionAnalysis", "IL3050", Justification = Constants.AvoidDynamicCodeRuntimeCheck)] | ||
| private static readonly IFormatterResolver[] Resolvers = DynamicAssembly.AvoidDynamicCode |
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.
@sbomer - Is it possible to add the FeatureGuardAttribute to PollyfillAttributes.cs and apply it to DynamicAssembly.AvoidDynamicCode? That way we wouldn't have to suppress these warnings?
I guess it may not be possible yet unless we are using a .NET 9 ILC and Roslyn Analyzers. Thoughts here?
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.
You could polyfill and add FeatureGuard if you made it a property and inverted it (UseDynamicCode for example). That would guarantee that it gets substituted to false in an AOT'd app that targets net9.0 or above. (And would also silence analyzer warnings if MessagePack were built for net9.0).
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.
If it only silences analyzer warnings when targeting net9.0, that won't work since I'm also targeting net8.0 and need to suppress warnings there anyway.
| { | ||
| this.Key = key; | ||
| this.SubType = Type.GetType(subType, throwOnError: true); | ||
| this.SubType = Type.GetType(subType, throwOnError: true) ?? throw new Exception("SubType not found"); |
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.
Since throwOnError is true, when would the new code ever get invoked?
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.
Perhaps never. But I added it to suppress a warning. Probably because Type.GetType(string, bool) is documented as returning Type?.
| #if NET8_0_OR_GREATER | ||
| [RequiresUnreferencedCode("The type named in 'subType' may be trimmed.")] | ||
| #endif | ||
| public UnionAttribute(int key, string subType) |
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.
You should be able to mark the subType with a DynamicallyAccessedMembers attribute, which will tell the trimmer that the string is a Type. And so any Type that is passed in on this string will be preserved.
I'm not sure on exactly which DynamicallyAccessedMemberTypes to use, it may work with just .None, or you may need to add PublicParameterlessConstructor. @sbomer and @agocke would know for sure. PublicParameterlessConstructor would be the safest to ensure it is kept.
| public UnionAttribute(int key, string subType) | |
| public UnionAttribute(int key, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] string subType) |
This would allow you to remove the RequiresUnreferencedCode attribute on this constructor.
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.
None didn't work. But PublicParameterlessConstructor worked. There's no guarantee that such a constructor is declared on this type, so I hope it works either way.
| public class MessagePackFormatterAttribute : Attribute | ||
| { | ||
| #if NET8_0_OR_GREATER | ||
| [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.Interfaces)] |
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.
I think you also need this attribute on the Type parameters passed into the constructors.
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.
That sounds reasonable. I wonder why the analyzer missed that.
| Deserializers.TryAdd(typeof(object), _ => (object p1, ref MessagePackReader p2, MessagePackSerializerOptions p3) => new object()); | ||
| } | ||
|
|
||
| #if NET8_0_OR_GREATER |
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.
(nit) Why are these are sometimes guarded with #if NET8 and sometimes not?
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.
Oh, just because I didn't switch to polyfill style attributes until I was midway through the work, and I missed removing the #if from some places. I removed it from here.
| { | ||
| // Note:This implementation is 'not' fastest, should more improve. | ||
| public sealed class EnumAsStringFormatter<T> : IMessagePackFormatter<T> | ||
| public sealed class EnumAsStringFormatter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] T> : IMessagePackFormatter<T> |
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.
Note: in net9 we changed our trimming rules such that when T is guaranteed to be an Enum (like it is here), it no longer warns when you try to get the fields of the enum type. I don't think adding this attribute is causing you problems now, but just an FYI. See dotnet/runtime#100814 for more info.
Co-authored-by: Eric Erhardt <[email protected]>
Co-authored-by: Eric Erhardt <[email protected]>
|
I'd imagine you might want to go the way of System Text Json and provide a source generator in the future and basically unroll the serialization loop? Would that be something you'd be interested if I can help contribute to? I think this PR goes a long way but for proper NativeAOT support in the future having a source generator that reads/writes direclty might be useful. |
|
@glennawatson Thanks for volunteering. We have a source generator. And it's very complex, and also relies on some runtime resolvers that are tainted with dynamic dependencies. I suspect we could eventually achieve NativeAOT safety (certified by satisfied analyzers) but it will be so much work that I started fresh at https://github.com/aarnott/nerdbank.messagepack. It is very nearly feature complete and is NativeAOT ready from the ground up. Much simpler code and much simpler to use, and still very fast. I'd love to hear what you think. |
|
Aborting this effort. Explanation here: #1997 (comment) |
NativeAOT support in MessagePack would be awesome for performance and to appeal to the .NET 9 crowd marching over from use of BinaryFormatter.