Files
Performance-Fish/Source/PerformanceFish/System/ReflectionCaching.cs
T

1124 lines
37 KiB
C#

// Copyright (c) 2023 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 System.Runtime.InteropServices;
using System.Security;
using System.Threading.Tasks;
using PerformanceFish.Cache;
using PerformanceFish.ModCompatibility;
using ConstructorInfoCache
= PerformanceFish.Cache.ByReference<System.RuntimeTypeHandle, System.Reflection.BindingFlags,
PerformanceFish.System.ReflectionCaching.ByValueComparableArray<System.Type>,
PerformanceFish.System.ReflectionCaching.TypePatches.ConstructorInfoCacheValue>;
using FieldGetters
= PerformanceFish.Cache.ByReferenceUnclearable<System.RuntimeFieldHandle,
PerformanceFish.System.ReflectionCaching.FieldInfoPatches.FieldInfoGetterCache>;
using FieldInfoCache
= PerformanceFish.Cache.ByReference<System.RuntimeTypeHandle, System.Reflection.BindingFlags, string,
PerformanceFish.System.ReflectionCaching.TypePatches.FieldInfoCacheValue>;
using FieldSetters
= PerformanceFish.Cache.ByReferenceUnclearable<System.RuntimeFieldHandle,
PerformanceFish.System.ReflectionCaching.FieldInfoPatches.FieldInfoSetterCache>;
using MethodInfoCache
= PerformanceFish.Cache.ByReference<System.RuntimeTypeHandle, System.Reflection.BindingFlags, string,
PerformanceFish.System.ReflectionCaching.TypePatches.MethodInfoCacheValue>;
using MethodInfoWithTypesAndFlagsCache
= PerformanceFish.Cache.ByReference<System.RuntimeTypeHandle, System.Reflection.BindingFlags, string,
PerformanceFish.System.ReflectionCaching.ByValueComparableArray<System.Type>,
PerformanceFish.System.ReflectionCaching.TypePatches.MethodInfoCacheValue>;
using MethodInfoWithTypesCache
= PerformanceFish.Cache.ByReference<System.RuntimeTypeHandle, string,
PerformanceFish.System.ReflectionCaching.ByValueComparableArray<System.Type>,
PerformanceFish.System.ReflectionCaching.TypePatches.MethodInfoCacheValue>;
using MethodInvokers
= PerformanceFish.Cache.ByReferenceUnclearable<System.RuntimeMethodHandle,
PerformanceFish.System.ReflectionCaching.MethodBasePatches.MethodInvokerCache>;
using PropertyInfoCache
= PerformanceFish.Cache.ByReference<System.RuntimeTypeHandle, System.Reflection.BindingFlags, string,
PerformanceFish.System.ReflectionCaching.TypePatches.PropertyInfoCacheValue>;
using CustomAttributeCache
= PerformanceFish.Cache.ByReference<System.Reflection.ICustomAttributeProvider, System.RuntimeTypeHandle, bool,
PerformanceFish.System.ReflectionCaching.MonoCustomAttrs.CustomAttributeCacheValue>;
using TypeFullNameCache
= PerformanceFish.Cache.ByReferenceUnclearable<System.RuntimeTypeHandle,
PerformanceFish.System.ReflectionCaching.TypePatches.TypeFullNameCacheValue>;
using ActivatorCache
= PerformanceFish.Cache.ByReferenceUnclearable<System.RuntimeTypeHandle,
PerformanceFish.System.ReflectionCaching.ActivatorPatches.ActivatorCacheValue>;
// using ActivatorWithArgumentsCache
// = PerformanceFish.Cache.ByReference<System.RuntimeTypeHandle,
// PerformanceFish.System.ReflectionCaching.ByValueComparableArray<System.Type>,
// PerformanceFish.System.ReflectionCaching.ActivatorPatches.ActivatorCacheValue>;
namespace PerformanceFish.System;
public sealed class ReflectionCaching : ClassWithFishPatches
{
static ReflectionCaching() // 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 record struct StateAndFlags
{
public bool State;
public BindingFlags Flags;
}
public sealed class TypePatches
{
public sealed class GetField_Patch : FishPatch
{
public override string Description { get; } = "Caches GetField lookups";
public override MethodBase TargetMethodInfo { get; }
= AccessTools.Method(typeof(RuntimeType), nameof(RuntimeType.GetField),
[typeof(string), typeof(BindingFlags)])!;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Prefix(Type __instance, string name, BindingFlags bindingAttr, out FieldInfo? __result,
out StateAndFlags __state)
{
var key = new FieldInfoCache(__instance.TypeHandle, bindingAttr,
ToLowerIfNeededForBindingFlags(name, bindingAttr));
__result = FieldInfoCache.Get.GetOrAdd(ref key).Info;
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;
UpdateCache(__instance, name, __result, __state);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void UpdateCache(Type __instance, string name, FieldInfo __result, StateAndFlags __state)
=> FieldInfoCache.GetExistingReference(__instance.TypeHandle, __state.Flags,
ToLowerIfNeededForBindingFlags(name, __state.Flags)).Info
= __result;
}
public sealed class GetMethodWithFlags_Patch : FishPatch
{
public override string Description { get; } = "Caches GetMethod lookups";
public override Expression<Action> TargetMethod { get; }
// ReSharper disable once ReturnValueOfPureMethodIsNotUsed
= static () => default(Type)!.GetMethod(null!, default(BindingFlags));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Prefix(Type __instance, string name, BindingFlags bindingAttr, out MethodInfo? __result,
out bool __state)
{
var key = new MethodInfoCache(__instance.TypeHandle, bindingAttr,
ToLowerIfNeededForBindingFlags(name, bindingAttr));
__result = MethodInfoCache.Get.GetOrAdd(ref key).Info;
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;
UpdateCache(__instance, name, bindingAttr, __result);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void UpdateCache(Type __instance, string name, BindingFlags bindingAttr, MethodInfo __result)
=> MethodInfoCache.GetExistingReference(__instance.TypeHandle, bindingAttr,
ToLowerIfNeededForBindingFlags(name, bindingAttr)).Info
= __result;
}
public sealed class GetMethodWithTypes_Patch : FishPatch
{
public override string Description { get; } = "Caches GetMethod lookups";
public override Expression<Action> TargetMethod { get; }
// ReSharper disable once ReturnValueOfPureMethodIsNotUsed
= static () => default(Type)!.GetMethod(null!, null!);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Prefix(Type __instance, string name, Type[] types, out MethodInfo? __result,
out bool __state)
{
var key = new MethodInfoWithTypesCache(__instance.TypeHandle, name, new(types));
__result = MethodInfoWithTypesCache.Get.GetOrAdd(ref key).Info;
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;
UpdateCache(__instance, name, types, __result);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void UpdateCache(Type __instance, string name, Type[] types, MethodInfo __result)
=> MethodInfoWithTypesCache.GetExistingReference(__instance.TypeHandle, name, new(types)).Info
= __result;
}
public sealed class GetMethodWithFlagsAndTypes_Patch : FishPatch
{
public override string Description { get; } = "Caches GetMethod lookups";
public override Expression<Action> TargetMethod { get; }
= static () => 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.GetOrAdd(ref key).Info;
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;
UpdateCache(__instance, name, types, bindingAttr, __result);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void UpdateCache(Type __instance, string name, Type[] types, BindingFlags bindingAttr,
MethodInfo __result)
=> MethodInfoWithTypesAndFlagsCache.GetExistingReference(__instance.TypeHandle, bindingAttr,
ToLowerIfNeededForBindingFlags(name, bindingAttr),
new(types)).Info = __result;
}
public sealed class GetConstructor_Patch : FishPatch
{
public override string Description { get; } = "Caches GetConstructor lookups";
public override Expression<Action> TargetMethod { get; }
= static () => 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.GetOrAdd(ref key).Info;
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;
UpdateCache(__instance, bindingAttr, types, __result);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void UpdateCache(Type __instance, BindingFlags bindingAttr, Type[] types,
ConstructorInfo __result)
=> ConstructorInfoCache.GetExistingReference(__instance.TypeHandle, bindingAttr, new(types)).Info
= __result;
}
public sealed class GetProperty_Patch : FishPatch
{
public override string Description { get; } = "Caches GetProperty lookups";
public override Expression<Action> TargetMethod { get; }
// ReSharper disable once ReturnValueOfPureMethodIsNotUsed
= static () => default(Type)!.GetProperty(null!, default(BindingFlags));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Prefix(Type __instance, string name, BindingFlags bindingAttr,
out PropertyInfo? __result, out bool __state)
{
var key = new PropertyInfoCache(__instance.TypeHandle, bindingAttr,
ToLowerIfNeededForBindingFlags(name, bindingAttr));
__result = PropertyInfoCache.Get.GetOrAdd(ref key).Info;
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;
UpdateCache(__instance, name, bindingAttr, __result);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void UpdateCache(Type __instance, string name, BindingFlags bindingAttr,
PropertyInfo __result)
=> PropertyInfoCache.GetExistingReference(__instance.TypeHandle, bindingAttr,
ToLowerIfNeededForBindingFlags(name, bindingAttr)).Info
= __result;
}
public sealed class GetFullName : FishPatch
{
public override string? Description { get; }
= "Normally averages at over 1000ns per call. Caching makes this about 30 times faster, including "
+ "Harmony overhead. Type.Name is similarly slow too, but unfortunately can't be patched as it's an "
+ "internal call. This has a relatively large load time impact.";
// https://github.com/Unity-Technologies/mono/blob/unity-2019.4-mbe/mono/metadata/icall.c#L2737-L2754
public override MethodBase TargetMethodInfo { get; }
= AccessTools.PropertyGetter(typeof(RuntimeType), nameof(RuntimeType.FullName));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Prefix(RuntimeType __instance, ref string? __result, out bool __state)
{
ref var cache = ref TypeFullNameCache.GetOrAddReference(__instance.TypeHandle);
if (!cache.Cached && !TryGetFromCentralCache(__instance, ref cache))
return __state = true;
__result = cache.Name;
return __state = false;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static bool TryGetFromCentralCache(RuntimeType instance,
ref TypeFullNameCacheValue cache)
{
lock (_lock)
{
ref var centralCache
= ref TypeFullNameCache.GetDirectly.GetOrAddReference(instance.TypeHandle);
cache.Name = centralCache.Name;
}
return cache.Cached;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Postfix(RuntimeType __instance, string? __result, bool __state)
{
if (!__state)
return;
UpdateCache(__instance, __result);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void UpdateCache(RuntimeType __instance, string? __result)
{
ref var cache = ref TypeFullNameCache.GetExistingReference(__instance.TypeHandle);
cache.Name = __result;
lock (_lock)
{
ref var centralCache
= ref TypeFullNameCache.GetDirectly.GetReference(__instance.TypeHandle);
centralCache.Name = __result;
}
}
private static object _lock = new();
}
// probably never actually needed
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[SuppressMessage("Globalization", "CA1308")]
private static string ToLowerIfNeededForBindingFlags(string name, BindingFlags bindingAttr)
=> (bindingAttr & BindingFlags.IgnoreCase) != 0 ? name.ToLowerInvariant() : name;
public record struct FieldInfoCacheValue
{
public FieldInfo? Info;
}
public record struct MethodInfoCacheValue
{
public MethodInfo? Info;
}
public record struct ConstructorInfoCacheValue
{
public ConstructorInfo? Info;
}
public record struct PropertyInfoCacheValue
{
public PropertyInfo? Info;
}
public record struct TypeFullNameCacheValue
{
private object? _value;
public TypeFullNameCacheValue() => _value = typeof(void);
public string? Name
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => Unsafe.As<string?>(_value);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set => _value = value;
}
public bool Cached
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _value is not MemberInfo;
}
}
}
public sealed class MonoCustomAttrs
{
public sealed class GetCustomAttributes : FishPatch
{
public override string? Description { get; }
= "Caches attributes for reflection lookups. Decent load time improvement.";
public override Delegate? TargetMethodGroup { get; }
= (Func<ICustomAttributeProvider, Type, bool, object[]>)global::System.MonoCustomAttrs
.GetCustomAttributes;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Prefix(ICustomAttributeProvider obj, Type attributeType, bool inherit,
out object[]? __result, out CustomAttributeState __state)
{
var key = new CustomAttributeCache(obj, (__state.AttributeType = attributeType).TypeHandle,
__state.Inherit = inherit);
ref var cache = ref CustomAttributeCache.GetOrAddReference(in key);
if (cache.Attributes is null)
TryGetFromCentralCache(ref key, ref cache);
return __state.State = (__result = cache.Attributes) is null;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void TryGetFromCentralCache(ref CustomAttributeCache key,
ref CustomAttributeCacheValue cache)
{
lock (_lock)
cache.Attributes = CustomAttributeCache.GetDirectly.GetOrAdd(ref key).Attributes;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Postfix(ICustomAttributeProvider obj, object[]? __result,
in CustomAttributeState __state)
{
if (!__state.State || __result is null)
return;
UpdateCache(obj, __state, __result);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void UpdateCache(ICustomAttributeProvider obj, in CustomAttributeState __state,
object[]? __result)
{
var key = new CustomAttributeCache(obj, __state.AttributeType.TypeHandle, __state.Inherit);
CustomAttributeCache.GetExistingReference(in key).Attributes = __result;
lock (_lock)
CustomAttributeCache.GetDirectly.GetReference(ref key).Attributes = __result;
}
private static object _lock = new();
}
public record struct CustomAttributeCacheValue
{
public object[]? Attributes;
}
public record struct CustomAttributeState
{
public bool State, Inherit;
public Type AttributeType;
}
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public record struct ByValueComparableArray<T>
where T : class
{
public byte Length;
private int _hashCode;
public T[] Array;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ByValueComparableArray(T[] array)
{
Guard.IsNotNull(array);
Guard.IsLessThanOrEqualTo(array.Length, byte.MaxValue);
Array = array;
Length = (byte)array.Length;
_hashCode = HashCode.Combine((int)Length, Length > 0 && Array[0] is { } item ? item.GetHashCode() : 0);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(ByValueComparableArray<T> other)
{
if (Length != other.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 int GetHashCode()
=> _hashCode;
}
public sealed class ActivatorPatches
{
public sealed class CreateInstance_Type : FishPatch
{
public override List<string> IncompatibleModIDs { get; } = [PackageIDs.MULTIPLAYER];
public override string? Description { get; }
= "Optimizes the parameterless Activator.CreateInstance method by invoking through specialized cached "
+ "delegates";
public override Delegate? TargetMethodGroup { get; } = (Func<Type, object>)Activator.CreateInstance;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Prefix(Type? type, ref object __result)
{
if (type is null)
return true;
ref var cache = ref ActivatorCache.GetOrAddReference(type.TypeHandle);
if (cache.Dirty
&& !ActivatorCache.UpdateAsyncCache<ActivatorCacheValue, Type, Func<object>>(ref cache,
type.TypeHandle, type))
{
return true;
}
__result = cache.Result!();
return false;
}
}
// public sealed class CreateInstance_Type_Args : FishPatch
// {
// public override Delegate? TargetMethodGroup { get; }
// = (Func<Type, object[], object>)Activator.CreateInstance;
// }
public record struct ActivatorCacheValue : IAsyncCacheable<RuntimeTypeHandle, Type, Func<object>>
{
public Func<object>? Result { get; set; }
public Task<Func<object>>? Task { get; set; }
public bool Dirty => Result == null;
public ValueTask<Func<object>?> MakeResultAsync(RuntimeTypeHandle key, Type type)
=> new(Reflection.CreateConstructorDelegate<Func<object>>(type));
}
// public record struct ActivatorWithArgumentsCacheValue : IAsyncCacheable<ActivatorWithArgumentsCache, Type,
// MethodBasePatches.MethodInvoker[]>
// {
// public MethodBasePatches.MethodInvoker[]? Result { get; set; }
// public Task<MethodBasePatches.MethodInvoker[]>? Task { get; set; }
// public bool Dirty => Result == null;
//
// public async ValueTask<MethodBasePatches.MethodInvoker[]?> MakeResultAsync(
// ActivatorWithArgumentsCache key, Type type)
// {
// var constructors = type.GetConstructors();
// var methodInvokerTasks = new List<Task<MethodBasePatches.MethodInvoker>>();
//
// foreach (var constructor in constructors)
// {
// var parameters = constructor.GetParameters();
//
// if (parameters.Length != key.Second.Length)
// continue;
//
// var parametersAreAssignable = true;
// for (var i = 0; i < parameters.Length; i++)
// {
// if (parameters[i].ParameterType.IsAssignableFrom(key.Second.Array[i]))
// parametersAreAssignable = false;
// }
//
// if (!parametersAreAssignable) // TODO: find the best constructor
// continue;
//
// methodInvokerTasks.Add(MethodInvokers
// .RequestFromCacheAsync<MethodBasePatches.MethodInvokerCache, MethodBase,
// MethodBasePatches.MethodInvoker>(constructor.MethodHandle, constructor));
// }
//
// var methodInvokers = new MethodBasePatches.MethodInvoker[methodInvokerTasks.Count];
// for (var i = 0; i < methodInvokerTasks.Count; i++)
// methodInvokers[i] = await methodInvokerTasks[i].ConfigureAwait(false);
//
// return methodInvokers;
// }
// }
}
// public static class Test // TODO: Toggle this when actually testing
// {
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
// public static bool GetValue(FieldInfo __instance, object? obj, ref object? __result)
// {
// ref var cache = ref FieldGetters.GetOrAddReference(__instance.FieldHandle);
//
// if (cache.Dirty)
// cache.Result = FieldInfoPatches.MakeGetterDelegate(__instance);
//
// __result = cache.Result!(obj);
// return false;
// }
//
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
// public static bool SetValue(FieldInfo __instance, object? obj, object? value)
// {
// ref var cache = ref FieldSetters.GetOrAddReference(__instance.FieldHandle);
//
// if (cache.Dirty)
// cache.Result = FieldInfoPatches.MakeSetterDelegate(__instance);
//
// cache.Result!(obj, value);
// return false;
// }
//
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
// public static bool Invoke(MethodBase __instance, object? obj, object[]? parameters, ref object? __result)
// {
// ref var cache = ref MethodInvokers.GetOrAddReference(__instance.MethodHandle);
//
// if (cache.Dirty)
// cache.Result = MethodBasePatches.MakeInvokeDelegate(__instance);
//
// __result = cache.Result!(obj, parameters);
// return false;
// }
// }
public sealed class FieldInfoPatches
{
public sealed class GetValue_Patch : FishPatch
{
public override string Description { get; }
= "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 { get; }
= AccessTools.Method(typeof(MonoField), nameof(MonoField.GetValue))!;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Prefix(FieldInfo __instance, object? obj, ref object? __result)
{
ref var cache = ref FieldGetters.GetOrAddReference(__instance.FieldHandle);
if (cache.Dirty
&& !FieldGetters.UpdateAsyncCache<FieldInfoGetterCache, FieldInfo, FieldGetter>(ref cache,
__instance.FieldHandle, __instance))
{
return true;
}
__result = cache.Result!(obj);
return false;
}
}
public sealed class SetValue_Patch : FishPatch
{
public override string Description { get; }
= "Optimizes the SetValue method by invoking it through specialized cached delegates";
public override Expression<Action> TargetMethod { get; }
= static () => default(FieldInfo)!.SetValue(null, null);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Prefix(FieldInfo __instance, object? obj, object? value)
{
ref var cache = ref FieldSetters.GetOrAddReference(__instance.FieldHandle);
if (cache.Dirty && !TryUpdate(__instance, ref cache))
return true;
cache.Result!(obj, value);
return false;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static bool TryUpdate(FieldInfo __instance, ref FieldInfoSetterCache cache)
=> cache.Task != _fieldSetterFailureTask && __instance.IsConst()
? ErrorForInvalidCallAttempt(__instance, ref cache)
: FieldSetters.UpdateAsyncCache<FieldInfoSetterCache, FieldInfo, FieldSetter>(ref cache,
__instance.FieldHandle, __instance);
[MethodImpl(MethodImplOptions.NoInlining)]
private static bool ErrorForInvalidCallAttempt(FieldInfo info, ref FieldInfoSetterCache cache)
{
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.");
return false;
}
}
private static Task<FieldSetter> _fieldSetterFailureTask = new(static () => null!);
public static FieldGetter? MakeGetterDelegate(FieldInfo info)
{
try
{
var dm = new DynamicMethod($"FieldGetter_{info.Name}", typeof(object), [typeof(object)],
GetOwnerType(info), true);
var il = dm.GetILGenerator();
TryEmitFieldInstanceArgument(il, info);
il.Emit(info.IsConst()
? 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 {
info.FullDescription()}. Reverting to default behaviour instead.\n{ex}\n{
Environment.StackTrace}");
return null;
}
}
public static FieldSetter? MakeSetterDelegate(FieldInfo info)
{
try
{
var dm = new DynamicMethod($"FieldSetter_{info.Name}", typeof(void),
[typeof(object), typeof(object)], GetOwnerType(info), true);
var il = dm.GetILGenerator();
TryEmitFieldInstanceArgument(il, info);
il.Emit(FishTranspiler.Argument(1));
if (info.FieldType.IsValueType)
{
il.Emit(FishTranspiler.Call(typeof(ReflectionCaching),
info.FieldType.IsNullable() ? nameof(UnboxNullableSafely) : nameof(UnboxSafely),
[typeof(object)],
[
info.FieldType.IsNullable() // TODO: verify that this is actually correct
? Nullable.GetUnderlyingType(info.FieldType)
: info.FieldType
]));
}
else
{
il.Emit(FishTranspiler.Call(typeof(ReflectionCaching), nameof(CastOrConvert),
generics: [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 {
info.FullDescription()}. Reverting to default behaviour instead.\n{ex}\n{
Environment.StackTrace}");
return null;
}
}
private static void TryEmitFieldInstanceArgument(ILGenerator il, FieldInfo info)
{
if (!info.IsStatic)
EmitInstanceArgument(il, info);
}
public record struct FieldInfoGetterCache : IAsyncCacheable<RuntimeFieldHandle, FieldInfo, FieldGetter>
{
public FieldGetter? Result { get; set; }
public Task<FieldGetter>? Task { get; set; }
public bool Dirty => Result == null;
public ValueTask<FieldGetter?> MakeResultAsync(RuntimeFieldHandle key, FieldInfo second)
=> new(MakeGetterDelegate(second));
}
public record struct FieldInfoSetterCache : IAsyncCacheable<RuntimeFieldHandle, FieldInfo, FieldSetter>
{
public FieldSetter? Result { get; set; }
public Task<FieldSetter>? Task { get; set; }
public bool Dirty => Result == null;
public ValueTask<FieldSetter?> MakeResultAsync(RuntimeFieldHandle key, FieldInfo second)
=> new(MakeSetterDelegate(second));
}
public delegate object? FieldGetter(object? obj);
public delegate void FieldSetter(object? obj, object? value);
}
public sealed class MethodBasePatches
{
public sealed class Invoke_Patch : FishPatch
{
public override bool DefaultState => false; // TODO: fix
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";
public override Expression<Action> TargetMethod { get; }
= static () => default(MethodBase)!.Invoke(null, null);
[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 (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;
// }
// }
if (cache.Dirty
&& !MethodInvokers.UpdateAsyncCache<MethodInvokerCache, MethodBase, MethodInvoker>(ref cache,
__instance.MethodHandle, __instance))
{
return true;
}
__result = cache.Result!(obj, parameters);
return false;
}
}
// Similar to HarmonyLib.MethodInvoker, but correctly handles a couple of edge cases Harmony would throw on.
[SuppressMessage("Globalization", "CA1310")]
public static MethodInvoker? MakeInvokeDelegate(MethodBase methodBase)
{
if (methodBase.DeclaringType is null && methodBase.Name.StartsWith("PadMethod"))
return null; // padding methods generated by harmony for linux
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 dm = new DynamicMethod($"MethodInvoker_{methodBase.Name}", typeof(object),
[typeof(object), typeof(object[])],
GetOwnerType(methodBase), true);
var il = dm.GetILGenerator();
var parameters = methodBase.GetParameters();
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));
il.Emit(FishTranspiler.Return);
return (MethodInvoker)dm.CreateDelegate(typeof(MethodInvoker));
}
catch (Exception ex)
{
Log.Warning($"PerformanceFish failed to generate an optimized delegate for {
Reflection.FullDescription(methodBase)}. Reverting to default behaviour instead.\n{ex}\n{
Environment.StackTrace}");
return null;
}
}
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]));
}
}
public record struct MethodInvokerCache : IAsyncCacheable<RuntimeMethodHandle, MethodBase, MethodInvoker>
{
public MethodInvoker? Result { get; set; }
public Task<MethodInvoker>? Task { get; set; }
public bool Dirty => Result == null;
public ValueTask<MethodInvoker?> MakeResultAsync(RuntimeMethodHandle key, MethodBase method)
=> new(MakeInvokeDelegate(method));
}
public delegate object? MethodInvoker(object? obj, object[]? parameters);
}
private static void EmitInstanceArgument(ILGenerator il, MemberInfo info)
{
var declaringType = info.DeclaringType!;
if (declaringType.IsValueType)
{
il.Emit(FishTranspiler.ArgumentAddress(0));
il.Emit(FishTranspiler.Call(typeof(ReflectionCaching), nameof(UnboxRefSafely), generics: [declaringType]));
}
else
{
il.Emit(FishTranspiler.This);
il.Emit(FishTranspiler.Call(typeof(ReflectionCaching), nameof(CastOrConvert), generics: [declaringType]));
}
}
private static Type GetOwnerType(MemberInfo info)
=> (info.DeclaringType ?? info.ReflectedType ?? typeof(void)) is var type
// ReSharper disable once MergeIntoPattern
&& type.IsInterface ? typeof(InterfaceCache)
: type.IsArray ? typeof(ArrayCache)
: type;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[SecuritySafeCritical]
public static ref TTo CastRefSafely<TTo>(ref object? from)
=> ref from is TTo or null
? ref Unsafe.As<object?, TTo>(ref from)
: ref ThrowInvalidCastException<TTo>();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[SecuritySafeCritical]
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.Type<object, TTo>(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static object? VerifyNullableBoxObject<TTo>(ref object? from) where TTo : struct
=> from is TTo
? from
: from = from is null
? default
: FisheryLib.Convert.Type<object, TTo?>(from);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[SecuritySafeCritical]
public static TTo? CastOrConvert<TTo>(object? from)
=> from is TTo or null
? Unsafe.As<object?, TTo?>(ref from)
: FisheryLib.Convert.Type<object, TTo>(from);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TTo UnboxSafely<TTo>(object? from) where TTo : struct
=> from switch
{
TTo to => to,
null => default,
_ => FisheryLib.Convert.Type<object, TTo>(from)
};
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TTo? UnboxNullableSafely<TTo>(object? from) where TTo : struct
=> from switch
{
TTo to => to,
null => null,
_ => FisheryLib.Convert.Type<object, TTo>(from)
};
[DoesNotReturn]
private static ref T ThrowInvalidCastException<T>() => throw new InvalidCastException();
public static class InterfaceCache
{
// populated with dynamic methods by MethodBasePatches.MakeInvokeDelegate, MakeGetterDelegate and
// MakeSetterDelegate
}
public static class ArrayCache
{
// populated with dynamic methods by MethodBasePatches.MakeInvokeDelegate, MakeGetterDelegate and
// MakeSetterDelegate
}
}