-
Notifications
You must be signed in to change notification settings - Fork 10.4k
Fix NativeAOT with minimal actions #35167
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,35 +15,64 @@ | |
|
||
namespace Microsoft.AspNetCore.Http | ||
{ | ||
internal static class TryParseMethodCache | ||
internal sealed class TryParseMethodCache | ||
{ | ||
private static readonly MethodInfo EnumTryParseMethod = GetEnumTryParseMethod(); | ||
private readonly MethodInfo _enumTryParseMethod; | ||
|
||
// Since this is shared source, the cache won't be shared between RequestDelegateFactory and the ApiDescriptionProvider sadly :( | ||
private static readonly ConcurrentDictionary<Type, Func<Expression, MethodCallExpression>?> MethodCallCache = new(); | ||
internal static readonly ParameterExpression TempSourceStringExpr = Expression.Variable(typeof(string), "tempSourceString"); | ||
private readonly ConcurrentDictionary<Type, Func<Expression, Expression>?> _methodCallCache = new(); | ||
|
||
public static bool HasTryParseMethod(ParameterInfo parameter) | ||
internal readonly ParameterExpression TempSourceStringExpr = Expression.Variable(typeof(string), "tempSourceString"); | ||
|
||
public TryParseMethodCache() : this(preferNonGenericEnumParseOverload: false) | ||
{ | ||
} | ||
|
||
// This is for testing | ||
public TryParseMethodCache(bool preferNonGenericEnumParseOverload) | ||
davidfowl marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
_enumTryParseMethod = GetEnumTryParseMethod(preferNonGenericEnumParseOverload); | ||
} | ||
|
||
public bool HasTryParseMethod(ParameterInfo parameter) | ||
{ | ||
var nonNullableParameterType = Nullable.GetUnderlyingType(parameter.ParameterType) ?? parameter.ParameterType; | ||
return FindTryParseMethod(nonNullableParameterType) is not null; | ||
} | ||
|
||
public static Func<Expression, MethodCallExpression>? FindTryParseMethod(Type type) | ||
public Func<Expression, Expression>? FindTryParseMethod(Type type) | ||
{ | ||
static Func<Expression, MethodCallExpression>? Finder(Type type) | ||
Func<Expression, Expression>? Finder(Type type) | ||
{ | ||
MethodInfo? methodInfo; | ||
|
||
if (type.IsEnum) | ||
{ | ||
methodInfo = EnumTryParseMethod.MakeGenericMethod(type); | ||
if (methodInfo != null) | ||
if (_enumTryParseMethod.IsGenericMethod) | ||
{ | ||
methodInfo = _enumTryParseMethod.MakeGenericMethod(type); | ||
|
||
return (expression) => Expression.Call(methodInfo!, TempSourceStringExpr, expression); | ||
} | ||
|
||
return null; | ||
return (expression) => | ||
{ | ||
var enumAsObject = Expression.Variable(typeof(object), "enumAsObject"); | ||
var success = Expression.Variable(typeof(bool), "success"); | ||
|
||
// object enumAsObject; | ||
// bool success; | ||
// success = Enum.TryParse(type, tempSourceString, out enumAsObject); | ||
// parsedValue = success ? (Type)enumAsObject : default; | ||
// return success; | ||
|
||
return Expression.Block(new[] { success, enumAsObject }, | ||
Expression.Assign(success, Expression.Call(_enumTryParseMethod, Expression.Constant(type), TempSourceStringExpr, enumAsObject)), | ||
Expression.Assign(expression, | ||
Expression.Condition(success, Expression.Convert(enumAsObject, type), Expression.Default(type))), | ||
success); | ||
}; | ||
|
||
} | ||
|
||
if (TryGetDateTimeTryParseMethod(type, out methodInfo)) | ||
|
@@ -87,32 +116,59 @@ public static bool HasTryParseMethod(ParameterInfo parameter) | |
return null; | ||
} | ||
|
||
return MethodCallCache.GetOrAdd(type, Finder); | ||
return _methodCallCache.GetOrAdd(type, Finder); | ||
} | ||
|
||
private static MethodInfo GetEnumTryParseMethod() | ||
private static MethodInfo GetEnumTryParseMethod(bool preferNonGenericEnumParseOverload) | ||
{ | ||
var staticEnumMethods = typeof(Enum).GetMethods(BindingFlags.Public | BindingFlags.Static); | ||
|
||
// With NativeAOT, if there's no static usage of Enum.TryParse<T>, it will be removed | ||
// we fallback to the non-generic version if that is the case | ||
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. Is there a reason the non-generic one does not get trimmed? 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. I assume it's because something else uses it. I have no idea what. 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 force it to be kept (e.g using 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. I'm not sure if it's needed TBH, I think 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. This is surprising to me. var staticEnumMethods = typeof(Enum).GetMethods(BindingFlags.Public | BindingFlags.Static); That should "mark" all public static methods on Is it possible to build up the 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.
Yes. It is a bug in NativeAOT: dotnet/runtimelab#1402 Once this bug gets fixed, this will be broken again. FWIW, I think fallbacks like this are wrong way to fix trimming and AOT friendliness issues. 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. I didn't realize this is going to return Yes, this is likely going to fail at 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.
Thanks. I'll look into that.
It just needs to work well enough (or barely work) to enable people to try it out and give feedback. Seems like we need something a bit more creative here (like a check for IsDynamicCodeCompiled maybe). Remember it's using the expression tree interpreter here so the bar is low 😄 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. IsDynamicCodeSupported is what I did in the DataContractSerializer as a temporary workaround while we figure things out: https://github.com/dotnet/runtimelab/pull/692/files#diff-5c36d3b2cc664a8e51d53ac77390c98eca051ca63b13443233afc38ccee5e65fR60-R64 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. Yep, this is what I'll do. FWIW I have a pretty good handle on most of the AOT friendly problems that will plague ASP.NET Core (like JSON 😄 ) and have prototypes for how to tackle most of them. |
||
MethodInfo? genericCandidate = null; | ||
MethodInfo? nonGenericCandidate = null; | ||
|
||
foreach (var method in staticEnumMethods) | ||
{ | ||
if (!method.IsGenericMethod || method.Name != nameof(Enum.TryParse) || method.ReturnType != typeof(bool)) | ||
if (method.Name != nameof(Enum.TryParse) || method.ReturnType != typeof(bool)) | ||
{ | ||
continue; | ||
} | ||
|
||
var tryParseParameters = method.GetParameters(); | ||
|
||
if (tryParseParameters.Length == 2 && | ||
// Enum.TryParse<T>(string, out object) | ||
if (method.IsGenericMethod && | ||
tryParseParameters.Length == 2 && | ||
tryParseParameters[0].ParameterType == typeof(string) && | ||
tryParseParameters[1].IsOut) | ||
{ | ||
return method; | ||
genericCandidate = method; | ||
} | ||
|
||
// Enum.TryParse(type, string, out object) | ||
if (!method.IsGenericMethod && | ||
tryParseParameters.Length == 3 && | ||
tryParseParameters[0].ParameterType == typeof(Type) && | ||
tryParseParameters[1].ParameterType == typeof(string) && | ||
tryParseParameters[2].IsOut) | ||
{ | ||
nonGenericCandidate = method; | ||
} | ||
} | ||
|
||
if (genericCandidate is null && nonGenericCandidate is null) | ||
{ | ||
Debug.Fail("No suitable System.Enum.TryParse method found."); | ||
throw new MissingMethodException("No suitable System.Enum.TryParse method found."); | ||
} | ||
|
||
if (preferNonGenericEnumParseOverload) | ||
{ | ||
return nonGenericCandidate!; | ||
} | ||
|
||
Debug.Fail("static bool System.Enum.TryParse<TEnum>(string? value, out TEnum result) not found."); | ||
throw new Exception("static bool System.Enum.TryParse<TEnum>(string? value, out TEnum result) not found."); | ||
return genericCandidate ?? nonGenericCandidate!; | ||
} | ||
|
||
private static bool TryGetDateTimeTryParseMethod(Type type, [NotNullWhen(true)] out MethodInfo? methodInfo) | ||
|
Uh oh!
There was an error while loading. Please reload this page.