Constrain reflection fast paths and preserve 1.6 tick behavior
This commit is contained in:
@@ -522,10 +522,13 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
||||
{
|
||||
public sealed class GetCustomAttributes : FishPatch
|
||||
{
|
||||
private static readonly Type RuntimeTypeType = typeof(object).GetType();
|
||||
|
||||
public override bool DefaultState => true;
|
||||
|
||||
public override string? Description { get; }
|
||||
= "Caches attributes for reflection lookups. Decent load time improvement.";
|
||||
= "Caches empty attribute lookups for fixed metadata. Nonempty results are recreated so attribute "
|
||||
+ "constructors and mutable attribute instances retain normal reflection behavior.";
|
||||
|
||||
public override Delegate? TargetMethodGroup { get; }
|
||||
= (Func<ICustomAttributeProvider, Type, bool, object[]>)global::System.MonoCustomAttrs
|
||||
@@ -535,7 +538,8 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
||||
public static bool Prefix(ICustomAttributeProvider obj, Type attributeType, bool inherit,
|
||||
out object[]? __result, out CustomAttributeState __state)
|
||||
{
|
||||
if (obj is null || attributeType is null)
|
||||
if (obj is null || attributeType is null || !HasFixedAttributeMetadata(obj)
|
||||
|| attributeType.GetType() != RuntimeTypeType || attributeType.Assembly.IsDynamic)
|
||||
{
|
||||
__state = default;
|
||||
__result = null;
|
||||
@@ -568,10 +572,7 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
||||
{
|
||||
var localLookup = TryUseCache(CustomAttributeCache.Get, ref key, out result);
|
||||
if (localLookup == CustomAttributeCacheLookup.Hit)
|
||||
{
|
||||
result = CopyCachedAttributes(result!);
|
||||
return CustomAttributeCacheLookup.Hit;
|
||||
}
|
||||
|
||||
if (localLookup == CustomAttributeCacheLookup.InProgress)
|
||||
return localLookup;
|
||||
@@ -581,7 +582,7 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
||||
var centralLookup = TryUseCache(CustomAttributeCache.GetDirectly, ref key, out var centralResult);
|
||||
if (centralLookup == CustomAttributeCacheLookup.Hit)
|
||||
{
|
||||
result = CopyCachedAttributes(centralResult!);
|
||||
result = centralResult;
|
||||
CustomAttributeCache.Get[key] = new()
|
||||
{
|
||||
Attributes = centralResult,
|
||||
@@ -605,7 +606,10 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
||||
if (!__state.ShouldStore || __result is null)
|
||||
return;
|
||||
|
||||
StoreCompleted(ref __state.Key, __result);
|
||||
if (__result.Length == 0)
|
||||
StoreCompleted(ref __state.Key, __result);
|
||||
else
|
||||
ClearInProgress(ref __state.Key);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
@@ -663,9 +667,7 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
||||
{
|
||||
var value = new CustomAttributeCacheValue
|
||||
{
|
||||
Attributes = result.Length == 0
|
||||
? (object[])Array.CreateInstance(Type.GetTypeFromHandle(key.Second), 0)
|
||||
: CopyCachedAttributes(result),
|
||||
Attributes = result,
|
||||
Status = CustomAttributeCacheStatus.Completed
|
||||
};
|
||||
|
||||
@@ -700,9 +702,16 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
||||
{
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static object[] CopyCachedAttributes(object[] attributes)
|
||||
=> attributes.Length != 0 ? (object[])attributes.Clone() : attributes;
|
||||
private static bool HasFixedAttributeMetadata(ICustomAttributeProvider provider)
|
||||
=> provider.GetType().Assembly == typeof(object).Assembly && (provider switch
|
||||
{
|
||||
MemberInfo member => !member.Module.Assembly.IsDynamic,
|
||||
ParameterInfo parameter => parameter.Member is MemberInfo member
|
||||
&& !member.Module.Assembly.IsDynamic,
|
||||
Assembly assembly => !assembly.IsDynamic,
|
||||
Module module => !module.Assembly.IsDynamic,
|
||||
_ => false
|
||||
});
|
||||
|
||||
private static object _lock = new();
|
||||
}
|
||||
@@ -1008,12 +1017,16 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
||||
|
||||
public sealed class FieldInfoPatches
|
||||
{
|
||||
private static readonly Type? RuntimeFieldInfoType
|
||||
= AccessTools.TypeByName("System.Reflection.RuntimeFieldInfo")
|
||||
?? AccessTools.TypeByName("System.Reflection.MonoField");
|
||||
|
||||
public sealed class GetValue_Patch : FishPatch
|
||||
{
|
||||
public override bool DefaultState => true;
|
||||
|
||||
public override string Description { get; }
|
||||
= "Optimizes the GetValue method by invoking it through specialized cached delegates";
|
||||
= "Uses cached getters for ordinary fields. Generic, dynamic and invalid calls use Mono reflection.";
|
||||
|
||||
//public GetValue_Patch()
|
||||
//{
|
||||
@@ -1033,7 +1046,12 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(FieldInfo __instance, object? obj, ref object? __result)
|
||||
{
|
||||
if (__instance.GetType() != RuntimeFieldInfoType)
|
||||
return true;
|
||||
|
||||
ref var cache = ref FieldGetters.GetOrAddReference(__instance.FieldHandle);
|
||||
if (!CanUseFieldFastPath(__instance, obj, ref cache.FastPath))
|
||||
return true;
|
||||
|
||||
if (cache.Dirty
|
||||
&& !FieldGetters.UpdateAsyncCache<FieldInfoGetterCache, FieldInfo, FieldGetter>(ref cache,
|
||||
@@ -1042,43 +1060,17 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
||||
return true;
|
||||
}
|
||||
|
||||
// Unsupported fields fall back to Mono's implementation, including its exception behavior.
|
||||
ValidateGetValueCall(__instance, obj);
|
||||
__result = cache.Result!(obj);
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ValidateGetValueCall(FieldInfo info, object? obj)
|
||||
{
|
||||
if (!info.IsStatic)
|
||||
{
|
||||
if (obj is null)
|
||||
throw new TargetException("Non-static field requires a target");
|
||||
|
||||
var declaringType = info.DeclaringType!;
|
||||
if (!declaringType.IsAssignableFrom(obj.GetType()))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"Field {info.Name} defined on type {declaringType} is not a field on the target object which is of type {obj.GetType()}.",
|
||||
nameof(obj));
|
||||
}
|
||||
}
|
||||
|
||||
if (!info.IsLiteral && info.DeclaringType!.ContainsGenericParameters)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Late bound operations cannot be performed on fields with types for which Type.ContainsGenericParameters is true.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class SetValue_Patch : FishPatch
|
||||
{
|
||||
public override bool DefaultState => false;
|
||||
public override bool DefaultState => true;
|
||||
|
||||
public override string Description { get; }
|
||||
= "Optimizes the SetValue method by invoking it through specialized cached delegates";
|
||||
= "Uses cached setters only for ordinary fields and already-compatible values. Other calls use Mono reflection.";
|
||||
|
||||
public override MethodBase? TargetMethodInfo { get; }
|
||||
= AccessTools.DeclaredMethod(typeof(FieldInfo), nameof(FieldInfo.SetValue),
|
||||
@@ -1087,7 +1079,13 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(FieldInfo __instance, object? obj, object? value)
|
||||
{
|
||||
if (__instance.GetType() != RuntimeFieldInfoType)
|
||||
return true;
|
||||
|
||||
ref var cache = ref FieldSetters.GetOrAddReference(__instance.FieldHandle);
|
||||
if (!CanUseFieldFastPath(__instance, obj, ref cache.FastPath, forSetter: true)
|
||||
|| !CanSetFieldExactly(ref cache.FastPath, value))
|
||||
return true;
|
||||
|
||||
if (cache.Dirty && !TryUpdate(__instance, ref cache))
|
||||
return true;
|
||||
@@ -1099,29 +1097,67 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static bool TryUpdate(FieldInfo __instance, ref FieldInfoSetterCache cache)
|
||||
{
|
||||
if (__instance.IsConst())
|
||||
return ErrorForInvalidCallAttempt(__instance, ref cache);
|
||||
|
||||
return FieldSetters.UpdateAsyncCache<FieldInfoSetterCache, FieldInfo, FieldSetter>(ref cache,
|
||||
__instance.FieldHandle, __instance);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static bool ErrorForInvalidCallAttempt(FieldInfo info, ref FieldInfoSetterCache cache)
|
||||
{
|
||||
if (cache.Task == _fieldSetterFailureTask)
|
||||
return false;
|
||||
|
||||
cache.Task = _fieldSetterFailureTask;
|
||||
Log.Error($"FieldInfo.SetValue has been called on '{
|
||||
info.FullDescription()}'. This is a const that gets copied at compile time. An attempt at "
|
||||
+ $"setting a value will always fail.\n{new StackTrace(2, true)}");
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static Task<FieldSetter> _fieldSetterFailureTask = new(static () => null!); // sentinel only
|
||||
public struct FieldFastPath
|
||||
{
|
||||
public bool Checked;
|
||||
public bool Unsupported;
|
||||
public bool IsStatic;
|
||||
public bool IsValueType;
|
||||
public Type? DeclaringType;
|
||||
public Type? FieldType;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool CanUseFieldFastPath(FieldInfo info, object? obj, ref FieldFastPath state,
|
||||
bool forSetter = false)
|
||||
{
|
||||
if (!state.Checked)
|
||||
InitializeFieldFastPath(info, ref state, forSetter);
|
||||
|
||||
if (state.Unsupported)
|
||||
return false;
|
||||
|
||||
if (state.IsStatic)
|
||||
return true;
|
||||
|
||||
return obj is not null && obj is not MarshalByRefObject
|
||||
&& (obj.GetType() == state.DeclaringType || state.DeclaringType!.IsInstanceOfType(obj));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool CanSetFieldExactly(ref FieldFastPath state, object? value)
|
||||
{
|
||||
if (value is null)
|
||||
return !state.IsValueType;
|
||||
|
||||
var fieldType = state.FieldType!;
|
||||
var valueType = value.GetType();
|
||||
return valueType == fieldType || !state.IsValueType && fieldType.IsAssignableFrom(valueType);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void InitializeFieldFastPath(FieldInfo info, ref FieldFastPath state, bool forSetter)
|
||||
{
|
||||
var declaringType = info.DeclaringType;
|
||||
var fieldType = info.FieldType;
|
||||
state.DeclaringType = declaringType;
|
||||
state.FieldType = fieldType;
|
||||
state.IsStatic = info.IsStatic;
|
||||
state.IsValueType = fieldType.IsValueType;
|
||||
// Constructed generic fields can share a RuntimeFieldHandle but need different delegates.
|
||||
state.Unsupported = declaringType is null || declaringType.IsGenericType
|
||||
|| declaringType.ContainsGenericParameters
|
||||
|| typeof(MarshalByRefObject).IsAssignableFrom(declaringType)
|
||||
|| info.Module.Assembly.IsDynamic || info.GetRequiredCustomModifiers().Length != 0
|
||||
|| info.IsLiteral || forSetter && info.IsInitOnly || fieldType.IsPointer
|
||||
|| fieldType.IsByRef || fieldType.ContainsGenericParameters;
|
||||
state.Checked = true;
|
||||
}
|
||||
|
||||
public static FieldGetter? MakeGetterDelegate(FieldInfo info)
|
||||
{
|
||||
@@ -1206,6 +1242,7 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
||||
{
|
||||
public FieldGetter? Result { get; set; }
|
||||
public Task<FieldGetter>? Task { get; set; }
|
||||
public FieldFastPath FastPath;
|
||||
|
||||
public bool Dirty => Result == null;
|
||||
|
||||
@@ -1217,6 +1254,7 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
||||
{
|
||||
public FieldSetter? Result { get; set; }
|
||||
public Task<FieldSetter>? Task { get; set; }
|
||||
public FieldFastPath FastPath;
|
||||
|
||||
public bool Dirty => Result == null;
|
||||
|
||||
@@ -1232,13 +1270,16 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
||||
|
||||
public sealed class MethodBasePatches
|
||||
{
|
||||
private static readonly Type? RuntimeMethodInfoType
|
||||
= AccessTools.TypeByName("System.Reflection.RuntimeMethodInfo")
|
||||
?? AccessTools.TypeByName("System.Reflection.MonoMethod");
|
||||
|
||||
public sealed class Invoke_Patch : FishPatch
|
||||
{
|
||||
public override bool DefaultState => false; // TODO: fix
|
||||
public override bool DefaultState => true;
|
||||
|
||||
public override string Description { get; }
|
||||
= "Optimizes the Invoke method by compiling specialized cached delegates. Disabled by default due to "
|
||||
+ "issues with various mods, like RIMMSqol and Performance Optimizer";
|
||||
= "Uses cached delegates for static, parameterless methods with fixed metadata. Other calls use Mono reflection.";
|
||||
|
||||
public override MethodBase? TargetMethodInfo { get; }
|
||||
= AccessTools.DeclaredMethod(typeof(MethodBase), nameof(MethodBase.Invoke),
|
||||
@@ -1247,22 +1288,21 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(MethodBase __instance, object? obj, object[]? parameters, ref object? __result)
|
||||
{
|
||||
ref var cache = ref MethodInvokers.GetOrAddReference(__instance.MethodHandle);
|
||||
if (__instance is not MethodInfo methodInfo
|
||||
|| methodInfo.GetType() != RuntimeMethodInfoType || parameters is { Length: > 0 })
|
||||
return true;
|
||||
|
||||
// if (cache.Dirty) // TODO: remove
|
||||
// {
|
||||
// if (OnStartup.State.Initialized)
|
||||
// {
|
||||
// Log.Message($"Called Invoke for {__instance.FullDescription()} with {obj.ToStringSafe()}, {
|
||||
// parameters?.Length.ToStringSafe() ?? "null"} parameters");
|
||||
// }
|
||||
//
|
||||
// if (!MethodInvokers.UpdateAsyncCache<MethodInvokerCache, MethodBase, MethodInvoker>(ref cache,
|
||||
// __instance.MethodHandle, __instance))
|
||||
// {
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
ref var cache = ref MethodInvokers.GetOrAddReference(__instance.MethodHandle);
|
||||
if (cache.Unsupported)
|
||||
return true;
|
||||
|
||||
if (!cache.EligibilityChecked)
|
||||
{
|
||||
cache.EligibilityChecked = true;
|
||||
cache.Unsupported = !CanCacheInvoke(methodInfo);
|
||||
if (cache.Unsupported)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (cache.Dirty
|
||||
&& !MethodInvokers.UpdateAsyncCache<MethodInvokerCache, MethodBase, MethodInvoker>(ref cache,
|
||||
@@ -1271,58 +1311,31 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
||||
return true;
|
||||
}
|
||||
|
||||
__result = cache.Result!(obj, parameters);
|
||||
try
|
||||
{
|
||||
__result = cache.Result!(obj, parameters);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new TargetInvocationException(ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Similar to HarmonyLib.MethodInvoker, but correctly handles a couple of edge cases Harmony would throw on.
|
||||
[SuppressMessage("Globalization", "CA1310")]
|
||||
// Restrict the delegate to a method whose reflection behavior can be reproduced cheaply.
|
||||
public static MethodInvoker? MakeInvokeDelegate(MethodBase methodBase)
|
||||
{
|
||||
if (methodBase.DeclaringType is null && methodBase.Name.StartsWith("PadMethod"))
|
||||
return null; // padding methods generated by harmony for linux
|
||||
|
||||
if (methodBase is not MethodInfo methodInfo || !CanCacheInvoke(methodInfo))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
var methodInfo = methodBase as MethodInfo;
|
||||
if (methodInfo is { ReturnType.IsByRef: true })
|
||||
return null; // how to box a ref return?
|
||||
|
||||
var constructorInfo = methodBase as ConstructorInfo;
|
||||
|
||||
if (methodInfo is null && constructorInfo is null)
|
||||
return null;
|
||||
|
||||
var parameters = methodBase.GetParameters();
|
||||
if (!CanCacheInvoke(methodBase, methodInfo, constructorInfo, parameters))
|
||||
return null;
|
||||
|
||||
var dm = new DynamicMethod($"MethodInvoker_{methodBase.Name}", typeof(object),
|
||||
[typeof(object), typeof(object[])],
|
||||
GetOwnerType(methodBase), true);
|
||||
var il = dm.GetILGenerator();
|
||||
|
||||
if (methodInfo != null && !methodBase.IsStatic)
|
||||
EmitInstanceArgument(il, methodBase);
|
||||
|
||||
for (var i = 0; i < parameters.Length; i++)
|
||||
{
|
||||
il.Emit(FishTranspiler.Argument(1));
|
||||
il.Emit(FishTranspiler.Constant(i));
|
||||
|
||||
var parameterType = parameters[i].ParameterType;
|
||||
|
||||
if (parameterType.IsByRef)
|
||||
EmitInvokeByRefParameterLoad(il, parameterType);
|
||||
else
|
||||
EmitInvokeParameterLoad(il, parameterType);
|
||||
}
|
||||
|
||||
if (methodInfo != null)
|
||||
EmitInvokeMethodCall(methodBase, il, methodInfo);
|
||||
else if (constructorInfo != null)
|
||||
il.Emit(FishTranspiler.New(constructorInfo));
|
||||
EmitInvokeMethodCall(methodInfo, il);
|
||||
|
||||
il.Emit(FishTranspiler.Return);
|
||||
|
||||
@@ -1337,109 +1350,45 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
||||
}
|
||||
}
|
||||
|
||||
private static bool CanCacheInvoke(MethodBase methodBase, MethodInfo? methodInfo,
|
||||
ConstructorInfo? constructorInfo, ParameterInfo[] parameters)
|
||||
private static bool CanCacheInvoke(MethodInfo methodInfo)
|
||||
{
|
||||
if (methodBase.ContainsGenericParameters
|
||||
|| (methodBase.CallingConvention & CallingConventions.VarArgs) != 0)
|
||||
try
|
||||
{
|
||||
var declaringType = methodInfo.DeclaringType;
|
||||
var returnType = methodInfo.ReturnType;
|
||||
// Constructed generic methods and types need more than a RuntimeMethodHandle as a key.
|
||||
return methodInfo.IsStatic && !methodInfo.IsGenericMethod
|
||||
&& declaringType is not null && !declaringType.IsGenericType
|
||||
&& !declaringType.ContainsGenericParameters
|
||||
&& !methodInfo.Module.Assembly.IsDynamic
|
||||
&& methodInfo.GetParameters().Length == 0
|
||||
&& !methodInfo.ContainsGenericParameters
|
||||
&& (methodInfo.CallingConvention & CallingConventions.VarArgs) == 0
|
||||
&& !returnType.IsByRef && !returnType.IsPointer
|
||||
&& !returnType.ContainsGenericParameters
|
||||
&& methodInfo.GetMethodBody() is not null;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (methodInfo != null && !IsSupportedInvokeType(methodInfo.ReturnType))
|
||||
return false;
|
||||
|
||||
if (constructorInfo?.DeclaringType is { } declaringType
|
||||
&& !IsSupportedInvokeType(declaringType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = 0; i < parameters.Length; i++)
|
||||
{
|
||||
if (!IsSupportedInvokeParameter(parameters[i].ParameterType))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsSupportedInvokeParameter(Type parameterType)
|
||||
private static void EmitInvokeMethodCall(MethodInfo methodInfo, ILGenerator il)
|
||||
{
|
||||
// object[] stores nullable value arguments as boxed T or null, not boxed Nullable<T>, so the
|
||||
// current byref fast path cannot safely hand out a Nullable<T>&.
|
||||
if (parameterType.IsByRef && parameterType.GetElementType()!.IsNullable())
|
||||
return false;
|
||||
|
||||
if (parameterType.IsByRef)
|
||||
parameterType = parameterType.GetElementType()!;
|
||||
|
||||
return IsSupportedInvokeType(parameterType);
|
||||
}
|
||||
|
||||
private static bool IsSupportedInvokeType(Type type)
|
||||
=> !type.IsPointer && !type.ContainsGenericParameters;
|
||||
|
||||
private static void EmitInvokeMethodCall(MethodBase methodBase, ILGenerator il, MethodInfo methodInfo)
|
||||
{
|
||||
il.Emit(FishTranspiler.Call(methodBase));
|
||||
|
||||
if (!methodInfo.ReturnType.IsValueType)
|
||||
return;
|
||||
|
||||
il.Emit(methodInfo.ReturnType == typeof(void)
|
||||
? FishTranspiler.Null
|
||||
: FishTranspiler.Box(methodInfo.ReturnType));
|
||||
}
|
||||
|
||||
private static void EmitInvokeParameterLoad(ILGenerator il, Type parameterType)
|
||||
{
|
||||
il.Emit(FishTranspiler.LoadElement<object>());
|
||||
|
||||
il.Emit(parameterType.IsValueType
|
||||
? FishTranspiler.Call(typeof(ReflectionCaching),
|
||||
parameterType.IsNullable() ? nameof(UnboxNullableSafely) : nameof(UnboxSafely),
|
||||
[typeof(object)],
|
||||
[
|
||||
parameterType.IsNullable()
|
||||
? Nullable.GetUnderlyingType(parameterType)
|
||||
: parameterType
|
||||
])
|
||||
: FishTranspiler.Call(typeof(ReflectionCaching), nameof(CastOrConvert),
|
||||
generics: [parameterType]));
|
||||
}
|
||||
|
||||
private static void EmitInvokeByRefParameterLoad(ILGenerator il, Type parameterType)
|
||||
{
|
||||
parameterType = parameterType.GetElementType()!;
|
||||
|
||||
il.Emit(FishTranspiler.LoadElementAddress<object>());
|
||||
|
||||
if (parameterType.IsNullable())
|
||||
{
|
||||
il.Emit(FishTranspiler.Call(typeof(ReflectionCaching), nameof(VerifyNullableBoxObject),
|
||||
generics: [Nullable.GetUnderlyingType(parameterType)]));
|
||||
|
||||
il.Emit(FishTranspiler.UnboxAddress(parameterType));
|
||||
}
|
||||
else
|
||||
{
|
||||
il.Emit(parameterType.IsValueType
|
||||
? FishTranspiler.Call(typeof(ReflectionCaching), nameof(UnboxRefSafely),
|
||||
generics:
|
||||
[
|
||||
/*parameterType.IsNullable()
|
||||
? Nullable.GetUnderlyingType(parameterType)
|
||||
:*/ parameterType
|
||||
])
|
||||
: FishTranspiler.Call(typeof(ReflectionCaching), nameof(CastRefSafely), generics: [parameterType]));
|
||||
}
|
||||
il.Emit(FishTranspiler.Call(methodInfo));
|
||||
if (methodInfo.ReturnType == typeof(void))
|
||||
il.Emit(FishTranspiler.Null);
|
||||
else if (methodInfo.ReturnType.IsValueType)
|
||||
il.Emit(FishTranspiler.Box(methodInfo.ReturnType));
|
||||
}
|
||||
|
||||
public record struct MethodInvokerCache : IAsyncCacheable<RuntimeMethodHandle, MethodBase, MethodInvoker>
|
||||
{
|
||||
public MethodInvoker? Result { get; set; }
|
||||
public Task<MethodInvoker>? Task { get; set; }
|
||||
public bool EligibilityChecked;
|
||||
public bool Unsupported;
|
||||
public bool Dirty => Result == null;
|
||||
|
||||
public ValueTask<MethodInvoker?> MakeResultAsync(RuntimeMethodHandle key, MethodBase method)
|
||||
|
||||
Reference in New Issue
Block a user