-
Notifications
You must be signed in to change notification settings - Fork 10k
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
[AOT] Enable analysis and annotate Http.Results #46082
Conversation
src/Http/Http.Results/src/RouteValueDictionaryTrimmerWarning.cs
Outdated
Show resolved
Hide resolved
else | ||
{ | ||
// Prioritize explicit implementation. | ||
var populateMetadataMethod = typeof(TTarget).GetMethod("Microsoft.AspNetCore.Http.Metadata.IEndpointMetadataProvider.PopulateMetadata", BindingFlags.Static | BindingFlags.NonPublic); |
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.
We should write tests for this.... As of now it is untested code, right?
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.
Yes. Has the IsDynamicCodeSupported arrived in aspnetcore?
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.
Yes, that one is in the current runtime we are using in aspnetcore. It's the DI switch that isn't here yet - blocked on a runtime bug in #46055.
Note even if the IsDynamicCodeSupported wasn't in, we could still write tests by making targeted console apps that are published for AOT and executed.
Seeing the changes to the results that generate links by using reflection on an object makes me think we should have generic overloads where we can mark them with DAM attributes. |
That would solve trimming. However, without dynamic code generation, the properties would still be accessed using slow reflection in AOT. I don't think we should encourage that. aspnetcore/src/Shared/PropertyHelper/PropertyHelper.cs Lines 214 to 243 in 4535ea1
|
Reflection isn’t slow with AOT. Now that we’re doing the work I think it’s time to quantify this on NativeAOT. cc @jkotas |
I am not sure how fast it is today. We have work planned for ,NET 8 to make reflection fast (on all runtimes). dotnet/designs#286 is the design document. cc @steveharter |
This PR should remove all Json related wanings #46008 |
e2494f1
to
4022f3d
Compare
I wrote some benchmarks: public class JitBenchmarks
{
[Benchmark]
public RouteValueDictionary RawDictionary() => new RouteValueDictionary { ["foo"] = "One", ["bar"] = "Two", ["baz"] = "Three" };
[Benchmark]
public RouteValueDictionary PropertyReflection() => new RouteValueDictionary(new { foo = "One", bar = "Two", baz = "Three" });
}
[SimpleJob(RuntimeMoniker.NativeAot80)]
public class AotBenchmarks
{
[Benchmark]
public RouteValueDictionary RawDictionary() => new RouteValueDictionary { ["foo"] = "One", ["bar"] = "Two", ["baz"] = "Three" };
[Benchmark]
public RouteValueDictionary PropertyReflection() => new RouteValueDictionary(new { foo = "One", bar = "Two", baz = "Three" });
}
public class Program
{
public static void Main(string[] args)
{
_ = BenchmarkRunner.Run(new[] { typeof (AotBenchmarks), typeof(JitBenchmarks) } );
}
} JIT:
AOT:
AOT seems generally slower, but native AOT reflection isn't that far off compiled expressions. My other concern is the number of overloads. We have
|
I thought about this more, and generics + If the value is |
Sounds like something we can ask @vitek-karas about? Seems like it could be fixable no? |
That should not be the case. Trimmers (both AOT and linker) have a special case behavior for void PrintProperties<[DynamicallyAccessedMembers(Properties)] T>()
{
foreach (var p in Nullable.GetUnderlyingType(typeof(T)).GetProperties()) { ... }
}
record A(int One, int Two);
struct record B(int One, int Two);
PrintProperties<A?>();
PrintProperties<B?>(); Without the special case there would be a very distinct behavioral difference between Do you have a specific example where this maybe doesn't work? /cc @jtschuster as the author of most of the relevant changes in the linker |
Ok! I didn't check this, I just guessed behavior. I'm sure I've seen linker warnings coming from doing The special handling of |
Damn, I found a scenario where we can't be trim-safe with DAM attribute. aspnetcore/src/Shared/PropertyHelper/PropertyHelper.cs Lines 510 to 521 in e523876
Unfortunately, that loses DAM information because you're calling For example: public interface IRoleUser : IUser
{
public string Role { get; set; }
}
public interface IUser
{
public string UserName { get; set; }
}
public RouteValueDictionary FromObject<TRouteValues>([DynamicallyAccessedMembers(Properties | Interfaces)] TRouteValues values)
{
return new RouteValueDictioanry(values);
}
IRoleUser user = new RoleUserImpl();
var routeValueDictionary = FromObject(user); Only the |
The It might still produce warnings though because trimming analysis doesn't see through collections, so |
Good to know. Thanks. Unfortunately, another challenge. Adding a generic argument for route values to public static IResult CreatedAtRoute(string? routeName = null, object? routeValues = null, object? value = null)
+ public static IResult CreatedAtRoute<TRouteValues>(string? routeName, TRouteValues routeValues, object? value = null)
public static IResult CreatedAtRoute<TValue>(string? routeName = null, object? routeValues = null, TValue? value = default)
+ public static IResult CreatedAtRoute<TValue, TRouteValues>(string? routeName, TRouteValues routeValues, TValue? value = default)
For example, this is ambigious: TypedResults.CreatedAtRoute<object>("RouteName", new { param1 = "param1Value" }, new User()); It's a source-breaking change, and the method call needs to be modified to one of: // both generic arguments
TypedResults.CreatedAtRoute<object, object>("RouteName", new { param1 = "param1Value" }, new User());
// or remove generic argument
TypedResults.CreatedAtRoute("RouteName", new { param1 = "param1Value" }, new User()); @davidfowl What do you think? I'm concerned about how many overloads there are with optional parameters. And generic arguments. I worry that there are undiscovered ambiguous method call situations today, and adding future overloads will be an ambiguous method call minefield. |
I think we can mark the existing methods as unsafe and discuss a way forward based on what you've discovered. @jaredpar this is a ref count for the API that lets us set an attribute to influence overload resolution for preferred methods. |
All feedback applied. Please take a look. |
src/Http/Http.Results/src/RouteValueDictionaryTrimmerWarning.cs
Outdated
Show resolved
Hide resolved
b584a0f
to
d17dd16
Compare
52e553c
to
5e670bc
Compare
d58333c
to
1c60cba
Compare
This reverts commit d7b1956.
All outstanding feedback applied. Please review 🙏 |
Co-authored-by: Eric Erhardt <eric.erhardt@microsoft.com>
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.
LGTM! I'm glad that Results<...> continues to work even if we're over-keeping members. I agree that adding a RemoteExecutor test for the non-IsDynamicCodeSupported-case makes sense.
@@ -139,9 +140,7 @@ public static ContentHttpResult Text(string? content, string? contentType, Encod | |||
/// <param name="contentType">The content type (MIME type).</param> | |||
/// <param name="statusCode">The status code to return.</param> | |||
/// <returns>The created <see cref="Utf8ContentHttpResult"/> object for the response.</returns> | |||
#pragma warning disable RS0027 // Public API with optional parameter(s) should have the most parameters amongst its public overloads. |
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.
Why were we able to remove these suppressions? Can we do the same in Results.cs?
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.
LGTM.
Thanks for the great work here, @JamesNK! I had some minor comments.
Fixes #46075
PR enables trimming and AOT analysis on
Microsoft.AspNetCore.Http.Results
and fixes warnings.object routeValues
. Reflection is used to get property values.Adds safe overloads of warning methods that takeFollow up task [AOT] Add safe route value overloads toRouteValueDictionary
. This is the same pattern we used forLinkGenerator
extension methods. Will need API review.Results
andTypeResults
#46229Adds a reflection fallback for calling staticPopulateMetadata
method.Response.WriteAsJsonAsync
andProblemDetails.Extensions
calls. I'm guessing the warnings will be removed from these with MakingJsonOptions
AOT/Trimmer-safe with EnsureJsonTrimmability switch #45886