Files
Performance-Fish/Source/PerformanceFish/System/ReflectionCaching.cs
T
2022-10-24 18:24:02 +02:00

638 lines
22 KiB
C#

// Copyright (c) 2022 bradson
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
using System.Diagnostics.CodeAnalysis;
using System.Reflection.Emit;
using ConstructorInfoCache = PerformanceFish.Cache.ByReference<System.RuntimeTypeHandle, System.Reflection.BindingFlags, PerformanceFish.System.ReflectionCaching.ByValueComparableArray<System.Type>, System.Reflection.ConstructorInfo>;
using FieldGetters = PerformanceFish.Cache.ByReference<System.RuntimeFieldHandle, PerformanceFish.System.ReflectionCaching.FieldInfoPatches.FieldInfoGetterCache>;
using FieldInfoCache = PerformanceFish.Cache.ByReference<System.RuntimeTypeHandle, System.Reflection.BindingFlags, string, System.Reflection.FieldInfo>;
using FieldSetters = PerformanceFish.Cache.ByReference<System.RuntimeFieldHandle, PerformanceFish.System.ReflectionCaching.FieldInfoPatches.FieldInfoSetterCache>;
using MethodInfoCache = PerformanceFish.Cache.ByReference<System.RuntimeTypeHandle, System.Reflection.BindingFlags, string, System.Reflection.MethodInfo>;
using MethodInfoWithTypesAndFlagsCache = PerformanceFish.Cache.ByReference<System.RuntimeTypeHandle, System.Reflection.BindingFlags, string, PerformanceFish.System.ReflectionCaching.ByValueComparableArray<System.Type>, System.Reflection.MethodInfo>;
using MethodInfoWithTypesCache = PerformanceFish.Cache.ByReference<System.RuntimeTypeHandle, string, PerformanceFish.System.ReflectionCaching.ByValueComparableArray<System.Type>, System.Reflection.MethodInfo>;
using MethodInvokers = PerformanceFish.Cache.ByReference<System.RuntimeMethodHandle, PerformanceFish.System.ReflectionCaching.MethodBasePatches.MethodInvokerCache>;
using PropertyInfoCache = PerformanceFish.Cache.ByReference<System.RuntimeTypeHandle, System.Reflection.BindingFlags, string, System.Reflection.PropertyInfo>;
namespace PerformanceFish.System;
public class ReflectionCaching : ClassWithFishPatches
{
public static void Initialize() // necessary to prevent recursion between patches and function pointer creation
{
try
{
ConstructorInfoCache.Initialize();
FieldGetters.Initialize();
FieldInfoCache.Initialize();
FieldSetters.Initialize();
MethodInfoCache.Initialize();
MethodInfoWithTypesAndFlagsCache.Initialize();
MethodInfoWithTypesCache.Initialize();
MethodInvokers.Initialize();
PropertyInfoCache.Initialize();
}
catch (Exception ex)
{
Log.Error($"Performance Fish encountered an exception while trying to initialize its ReflectionCaching:\n{ex}");
}
}
public struct StateAndFlags
{
public bool State;
public BindingFlags Flags;
}
public class TypePatches
{
public class GetField_Patch : FishPatch
{
public override string Description => "Caches GetField lookups";
public override MethodBase TargetMethodInfo
=> Reflection.MethodInfo("mscorlib", "System.RuntimeType", nameof(Type.GetField), new[] { typeof(string), typeof(BindingFlags) });
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Prefix(Type __instance, string name, BindingFlags bindingAttr, ref FieldInfo? __result, out StateAndFlags __state)
{
var key = new FieldInfoCache(__instance.TypeHandle, bindingAttr, ToLowerIfNeededForBindingFlags(name, bindingAttr));
__result = FieldInfoCache.Get.TryGetValue(ref key);
if (__result is null)
{
__state = new() { State = true, Flags = bindingAttr };
return true;
}
else
{
__state = new() { State = false, Flags = bindingAttr };
return false;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Postfix(Type __instance, string name, FieldInfo __result, StateAndFlags __state)
{
if (!__state.State || __result is null)
return;
FieldInfoCache.Get[new(__instance.TypeHandle, __state.Flags, ToLowerIfNeededForBindingFlags(name, __state.Flags))] = __result;
}
}
public class GetMethodWithFlags_Patch : FishPatch
{
public override string Description => "Caches GetMethod lookups";
public override Expression<Action> TargetMethod => () => default(Type)!.GetMethod(null, default(BindingFlags));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Prefix(Type __instance, string name, BindingFlags bindingAttr, ref MethodInfo? __result, out bool __state)
{
var key = new MethodInfoCache(__instance.TypeHandle, bindingAttr, ToLowerIfNeededForBindingFlags(name, bindingAttr));
__result = MethodInfoCache.Get.TryGetValue(ref key);
return __state = __result is null;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Postfix(Type __instance, string name, BindingFlags bindingAttr, MethodInfo __result, bool __state)
{
if (!__state || __result is null)
return;
MethodInfoCache.Get[new(__instance.TypeHandle, bindingAttr, ToLowerIfNeededForBindingFlags(name, bindingAttr))] = __result;
}
}
public class GetMethodWithTypes_Patch : FishPatch
{
public override string Description => "Caches GetMethod lookups";
public override Expression<Action> TargetMethod => () => default(Type)!.GetMethod(null, null);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Prefix(Type __instance, string name, Type[] types, ref MethodInfo? __result, out bool __state)
{
var key = new MethodInfoWithTypesCache(__instance.TypeHandle, name, new(types));
__result = MethodInfoWithTypesCache.Get.TryGetValue(ref key);
return __state = __result is null;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Postfix(Type __instance, string name, Type[] types, MethodInfo __result, bool __state)
{
if (!__state || __result is null)
return;
MethodInfoWithTypesCache.Get[new(__instance.TypeHandle, name, new(types))] = __result;
}
}
public class GetMethodWithFlagsAndTypes_Patch : FishPatch
{
public override string Description => "Caches GetMethod lookups";
public override Expression<Action> TargetMethod => () => default(Type)!.GetMethod(null, default, null, null, null);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Prefix(Type __instance, string name, BindingFlags bindingAttr, Binder binder, Type[] types, ParameterModifier[] modifiers, ref MethodInfo? __result, out bool __state)
{
if (binder != null || modifiers is { Length: > 0 })
{
__state = false;
return true;
}
var key = new MethodInfoWithTypesAndFlagsCache(__instance.TypeHandle, bindingAttr, ToLowerIfNeededForBindingFlags(name, bindingAttr), new(types));
__result = MethodInfoWithTypesAndFlagsCache.Get.TryGetValue(ref key);
return __state = __result is null;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Postfix(Type __instance, string name, Type[] types, BindingFlags bindingAttr, MethodInfo __result, bool __state)
{
if (!__state || __result is null)
return;
MethodInfoWithTypesAndFlagsCache.Get[new(__instance.TypeHandle, bindingAttr, ToLowerIfNeededForBindingFlags(name, bindingAttr), new(types))] = __result;
}
}
public class GetConstructor_Patch : FishPatch
{
public override string Description => "Caches GetConstructor lookups";
public override Expression<Action> TargetMethod => () => default(Type)!.GetConstructor(default, null, null, null);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Prefix(Type __instance, BindingFlags bindingAttr, Binder binder, Type[] types, ParameterModifier[] modifiers, ref ConstructorInfo? __result, out bool __state)
{
if (binder != null || modifiers is { Length: > 0 })
{
__state = false;
return true;
}
var key = new ConstructorInfoCache(__instance.TypeHandle, bindingAttr, new(types));
__result = ConstructorInfoCache.Get.TryGetValue(ref key);
return __state = __result is null;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Postfix(Type __instance, BindingFlags bindingAttr, Type[] types, ConstructorInfo __result, bool __state)
{
if (!__state || __result is null)
return;
ConstructorInfoCache.Get[new(__instance.TypeHandle, bindingAttr, new(types))] = __result;
}
}
public class GetProperty_Patch : FishPatch
{
public override string Description => "Caches GetProperty lookups";
public override Expression<Action> TargetMethod => () => default(Type)!.GetProperty(null, default(BindingFlags));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Prefix(Type __instance, string name, BindingFlags bindingAttr, ref PropertyInfo? __result, out bool __state)
{
var key = new PropertyInfoCache(__instance.TypeHandle, bindingAttr, ToLowerIfNeededForBindingFlags(name, bindingAttr));
__result = PropertyInfoCache.Get.TryGetValue(ref key);
return __state = __result is null;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Postfix(Type __instance, string name, BindingFlags bindingAttr, PropertyInfo __result, bool __state)
{
if (!__state || __result is null)
return;
PropertyInfoCache.Get[new(__instance.TypeHandle, bindingAttr, ToLowerIfNeededForBindingFlags(name, bindingAttr))] = __result;
}
}
// probably never actually needed
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static string ToLowerIfNeededForBindingFlags(string name, BindingFlags bindingAttr) => (bindingAttr & BindingFlags.IgnoreCase) != 0 ? name.ToLowerInvariant() : name;
}
public struct ByValueComparableArray<T> : IEquatable<ByValueComparableArray<T>>
where T : class
{
public T[] Array;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ByValueComparableArray(T[] array)
{
Guard.IsNotNull(array);
Array = array;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(ByValueComparableArray<T> other)
{
var length = Array.Length;
if (length != other.Array.Length)
return false;
for (var i = 0; i < length; i++)
{
if (!Array[i].Equals(other.Array[i]))
return false;
}
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(object obj) => obj is ByValueComparableArray<T> array && Equals(array);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode() => HashCode.Combine(Array.Length, Array.Length > 0 && Array[0] is { } item ? item.GetHashCode() : 0);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(ByValueComparableArray<T> left, ByValueComparableArray<T> right) => left.Equals(right);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(ByValueComparableArray<T> left, ByValueComparableArray<T> right) => !(left == right);
}
#if eee == false
public class FieldInfoPatches
{
public class GetValue_Patch : FishPatch
{
public override string Description => "Optimizes the GetValue method by invoking it through specialized cached delegates";
//public GetValue_Patch()
//{
// var types = AllSubclassesNonAbstract(typeof(FieldInfo))
// .SelectMany(t
// => DeclaredMethodOrNothing(t, nameof(FieldInfo.GetValue), new[] { typeof(object) }))
// .Select(m => m.DeclaringType);
// Log.Message(types.ToStringSafeEnumerable());
//}
public override MethodBase TargetMethodInfo => Reflection.MethodInfo("mscorlib", "System.Reflection.MonoField", nameof(FieldInfo.GetValue));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Prefix(FieldInfo __instance, object obj, ref object __result)
{
ref var cache = ref FieldGetters.Get.TryGetReferenceUnsafe(__instance.FieldHandle);
if (!Unsafe.IsNullRef(ref cache))
{
if (cache.func != null || UpdateGetterCache(ref cache, __instance))
{
__result = cache.func!(obj);
return false;
}
return true;
}
else
{
return AddCache(__instance);
}
}
private static bool AddCache(FieldInfo __instance)
{
FieldGetters.Get.Add(__instance.FieldHandle, new());
return true;
}
}
public class SetValue_Patch : FishPatch
{
public override string Description => "Optimizes the SetValue method by invoking it through specialized cached delegates";
public override Expression<Action> TargetMethod => () => default(FieldInfo)!.SetValue(null, null);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Prefix(FieldInfo __instance, object obj, object value)
{
ref var cache = ref FieldSetters.Get.TryGetReferenceUnsafe(__instance.FieldHandle);
if (!Unsafe.IsNullRef(ref cache))
{
if (cache.action != null || UpdateSetterCache(ref cache, __instance))
{
cache.action!(obj, value);
return false;
}
return true;
}
else
{
return AddCache(__instance);
}
}
private static bool AddCache(FieldInfo __instance)
{
FieldSetters.Get.Add(__instance.FieldHandle, new());
return true;
}
}
public static bool UpdateGetterCache(ref FieldInfoGetterCache cache, FieldInfo instance)
{
cache.callCount++;
if (cache.callCount == 300)
cache.func = MakeGetterDelegate(instance);
else if (cache.callCount > 300)
return false;
FieldGetters.Get[instance.FieldHandle] = cache;
return cache.func != null;
}
public static bool UpdateSetterCache(ref FieldInfoSetterCache cache, FieldInfo instance)
{
cache.callCount++;
if (cache.callCount == 300)
cache.action = MakeSetterDelegate(instance);
else if (cache.callCount > 300)
return false;
FieldSetters.Get[instance.FieldHandle] = cache;
return cache.action != null;
}
private static FieldGetter? MakeGetterDelegate(FieldInfo info)
{
try
{
var dm = new DynamicMethod($"FieldGetter_{info.Name}", typeof(object), new[] { typeof(object) }, info.DeclaringType, true);
var il = dm.GetILGenerator();
if (!info.IsStatic)
{
il.Emit(FishTranspiler.This);
il.Emit(info.DeclaringType.IsValueType
? FishTranspiler.Unbox(info.DeclaringType)
: FishTranspiler.Cast(info.DeclaringType));
}
il.Emit(
/*info.DeclaringType.IsEnum
&& info.DeclaringType.GetEnumUnderlyingType() is var enumType
? enumType == typeof(long) || enumType == typeof(ulong)
? FishTranspiler.Constant((long)info.GetValue(info.DeclaringType))
: FishTranspiler.Constant((int)info.GetValue(info.DeclaringType))
:*/ info.IsLiteral && !info.IsInitOnly ? FishTranspiler.Constant(info.GetRawConstantValue())
: FishTranspiler.Field(info));
if (info.FieldType.IsValueType)
il.Emit(FishTranspiler.Box(info.FieldType));
il.Emit(FishTranspiler.Return);
return (FieldGetter)dm.CreateDelegate(typeof(FieldGetter));
}
catch (Exception ex)
{
Log.Error($"PerformanceFish failed to generate an optimized delegate for {GetFieldDescription(info)}. Reverting to default behaviour instead.\n{ex}");
return null;
}
}
private static FieldSetter? MakeSetterDelegate(FieldInfo info)
{
try
{
var dm = new DynamicMethod($"FieldSetter_{info.Name}", typeof(void), new[] { typeof(object), typeof(object) }, info.DeclaringType, true);
var il = dm.GetILGenerator();
if (!info.IsStatic)
{
il.Emit(FishTranspiler.This);
il.Emit(info.DeclaringType.IsValueType
? FishTranspiler.Unbox(info.DeclaringType)
: FishTranspiler.Cast(info.DeclaringType));
}
il.Emit(FishTranspiler.Argument(1));
if (info.FieldType.IsValueType)
{
//il.Emit(FishTranspiler.UnboxAny(info.FieldType));
il.Emit(FishTranspiler.Call(typeof(ReflectionCaching), info.FieldType.IsNullable() ? nameof(UnboxNullableSafely) : nameof(UnboxSafely),
new[] { typeof(object) },
new[] { info.FieldType.IsNullable() ? Nullable.GetUnderlyingType(info.FieldType) : info.FieldType }));
}
else
{
il.Emit(FishTranspiler.Cast(info.FieldType));
}
il.Emit(FishTranspiler.StoreField(info));
il.Emit(FishTranspiler.Return);
return (FieldSetter)dm.CreateDelegate(typeof(FieldSetter));
}
catch (Exception ex)
{
Log.Error($"PerformanceFish failed to generate an optimized delegate for {GetFieldDescription(info)}. Reverting to default behaviour instead.\n{ex}");
return null;
}
}
public struct FieldInfoGetterCache
{
public FieldGetter? func;
public int callCount;
}
public struct FieldInfoSetterCache
{
public FieldSetter? action;
public int callCount;
}
public delegate object FieldGetter(object obj);
public delegate void FieldSetter(object obj, object value);
}
private static string GetFieldDescription(FieldInfo info) => $"{(info.IsPublic ? "public" : "private")} {(info.IsLiteral && !info.IsInitOnly ? "const " : info.IsStatic ? "static " : "")} {info.FieldType.FullDescription()} {info.DeclaringType.FullDescription()}:{info.Name}";
public class MethodBasePatches
{
public class Invoke_Patch : FishPatch
{
public override string Description => "Optimizes the Invoke method by invoking it through specialized cached delegates";
public override Expression<Action> TargetMethod => () => default(MethodBase)!.Invoke(null, null);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Prefix(MethodBase __instance, object obj, object[] parameters, ref object __result)
{
if (__instance is not MethodInfo method)
return true;
ref var cache = ref MethodInvokers.Get.TryGetReferenceUnsafe(method.MethodHandle);
if (!Unsafe.IsNullRef(ref cache))
{
if (cache.func != null || UpdateCache(ref cache, method))
{
__result = cache.func!(obj, parameters);
return false;
}
return true;
}
else
{
return AddCache(method);
}
}
private static bool AddCache(MethodInfo method)
{
MethodInvokers.Get.Add(method.MethodHandle, new());
return true;
}
}
public static bool UpdateCache(ref MethodInvokerCache cache, MethodInfo instance)
{
cache.callCount++;
if (cache.callCount == 300)
cache.func = MakeInvokeDelegate(instance);
else if (cache.callCount > 300)
return false;
MethodInvokers.Get[instance.MethodHandle] = cache;
return cache.func != null;
}
// HarmonyLib.MethodInvoker can't handle arrays with null values when used with ByRef value types. That aside this is near identical.
public static MethodInvoker? MakeInvokeDelegate(MethodInfo info)
{
try
{
//DebugLog.Message($"Making delegate for {(info.IsStatic ? "static" : "")} {info.DeclaringType} {info} with arguments {info.GetParameters().ToStringSafeEnumerable()}");
var dm = new DynamicMethod($"MethodInvoker_{info.Name}", typeof(object), new[] { typeof(object), typeof(object[]) }, info.DeclaringType, true);
var il = dm.GetILGenerator();
var parameters = info.GetParameters();
if (!info.IsStatic)
{
il.Emit(FishTranspiler.This);
if (info.DeclaringType.IsValueType)
il.Emit(FishTranspiler.UnboxAny(info.DeclaringType));
}
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)
{
parameterType = parameterType.GetElementType();
il.Emit(FishTranspiler.LoadElementAddress<object>());
il.Emit(parameterType.IsValueType
? FishTranspiler.Call(typeof(ReflectionCaching), nameof(UnboxRefSafely),
new[] { typeof(object).MakeByRefType() },
new[] { parameterType.IsNullable() ? Nullable.GetUnderlyingType(parameterType) : parameterType })
: FishTranspiler.Call(typeof(ReflectionCaching), nameof(CastRefSafely),
new[] { typeof(object).MakeByRefType() },
new[] { typeof(object), parameterType }));
}
else
{
il.Emit(FishTranspiler.LoadElement<object>());
il.Emit(parameterType.IsValueType
? FishTranspiler.Call(typeof(ReflectionCaching), parameterType.IsNullable() ? nameof(UnboxNullableSafely) : nameof(UnboxSafely),
new[] { typeof(object) },
new[] { parameterType.IsNullable() ? Nullable.GetUnderlyingType(parameterType) : parameterType })
: FishTranspiler.As(parameterType));
}
}
il.Emit(FishTranspiler.Call(info));
if (info.ReturnType == typeof(void))
il.Emit(FishTranspiler.Null);
else if (info.ReturnType.IsValueType)
il.Emit(FishTranspiler.Box(info.ReturnType));
il.Emit(FishTranspiler.Return);
return (MethodInvoker)dm.CreateDelegate(typeof(MethodInvoker));
}
catch (Exception ex)
{
Log.Error($"PerformanceFish failed to generate an optimized delegate for {info.FullDescription()}. Reverting to default behaviour instead.\n{ex}");
return null;
}
}
public struct MethodInvokerCache
{
public MethodInvoker? func;
public int callCount;
}
public delegate object MethodInvoker(object obj, object[] parameters);
}
#endif
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref TTo CastRefSafely<TFrom, TTo>(ref TFrom from)
=> ref from is TTo or null
? ref Unsafe.As<TFrom, TTo>(ref from)
: ref ThrowInvalidCastException<TTo>();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref TTo UnboxRefSafely<TTo>(ref object from) where TTo : struct
=> ref from is TTo
? ref Unsafe.Unbox<TTo>(from)
: ref Unsafe.Unbox<TTo>(from
= from is null
? default
: FisheryLib.Convert.To<IConvertible, TTo>((IConvertible)from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TTo UnboxSafely<TTo>(object from) where TTo : struct
=> from is TTo to
? to
// : from is IConvertible
: FisheryLib.Convert.To<IConvertible, TTo>((IConvertible)from);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TTo? UnboxNullableSafely<TTo>(object from) where TTo : struct
=> from is TTo to
? to
: from is null
? null
// : from is IConvertible
: FisheryLib.Convert.To<IConvertible, TTo>((IConvertible)from);
[DoesNotReturn]
private static ref T ThrowInvalidCastException<T>() => throw new InvalidCastException();
}