Files
Fishery/Source/FisheryLib/FunctionPointers/MethodFactory.cs
T
bbradson cdf209eb02 Added FisheryLib.Collections.FishTable and Mono.Cecil extension methods
A bit of refactoring has been done too
2023-06-03 20:10:58 +02:00

393 lines
12 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.Collections.Concurrent;
using System.Diagnostics;
using System.Linq;
using System.Reflection.Emit;
using System.Security;
namespace FisheryLib.FunctionPointers;
public abstract class MethodFactory
{
protected readonly MethodInfo[] _methods;
protected readonly ConcurrentDictionary<Type, MethodInfo> _methodsByType = new();
protected readonly string _namePrefix;
protected readonly bool _byRef;
public MethodInfo GetGeneric(string name, params Type[] typeArguments)
=> GetNamed(name).MakeGenericMethod(typeArguments);
public MethodInfo GetNamed(string name)
{
foreach (var method in _methods)
{
if (method.Name == name)
return method;
}
return ThrowHelper.ThrowArgumentException<MethodInfo>($"No method found with name: {name}");
}
protected MethodInfo? GetSpecializedMethod(Type type)
{
if (_byRef)
type = type.MakeByRefType();
for (var i = 0; i < _methods.Length; i++)
{
var method = _methods[i];
if (!method.IsGenericMethod && method.GetParameters()[0].ParameterType == type)
return method;
}
return null;
}
public MethodInfo GetForType(Type type)
=> _methodsByType.TryGetValue(type, out var value)
? value
: _methodsByType[type] = GetOrMakeMethodForType(type);
protected abstract MethodInfo GetOrMakeMethodForType(Type type);
[SecuritySafeCritical]
public IntPtr GetFunctionPointer(Type type) => GetForType(type).GetFunctionPointer();
protected static MethodInfo[] GetMethodInfoArray(Type type, Func<MethodInfo, bool> predicate)
=> type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.DeclaredOnly)
.Where(predicate).ToArray();
protected MethodFactory(MethodInfo[] methods, string namePrefix, bool byRef)
{
_byRef = byRef;
_methods = methods;
_namePrefix = namePrefix;
}
public new class Equals : MethodFactory
{
protected override MethodInfo GetOrMakeMethodForType(Type type)
=> GetSpecializedMethod(type)
?? (type.IsAssignableTo(typeof(IEquatable<>), type)
? GetGeneric(type.IsValueType
? nameof(FunctionPointers.Equals.Methods.EquatableValueType)
: nameof(FunctionPointers.Equals.Methods.EquatableReferenceType), type)
: type.IsNullable(out var genericArgument)
&& genericArgument.IsAssignableTo(typeof(IEquatable<>), genericArgument)
? GetGeneric(nameof(FunctionPointers.Equals.Methods.Nullable), genericArgument)
: type.IsEnum
? GetGeneric(Type.GetTypeCode(Enum.GetUnderlyingType(type))
switch
{
TypeCode.Int16
or TypeCode.SByte
or TypeCode.Byte
or TypeCode.UInt16
or TypeCode.Int32
or TypeCode.UInt32 => nameof(FunctionPointers.Equals.Methods.Enum),
TypeCode.Int64
or TypeCode.UInt64 => nameof(FunctionPointers.Equals.Methods.LongEnum),
_ => ThrowHelper.ThrowNotSupportedException<string>()
}, type)
: type.IsValueType
? TryGetMethodByName(type) ?? CompileEqualsMethod(type)
: GetGeneric(nameof(FunctionPointers.Equals.Methods.Object), type));
protected MethodInfo? TryGetMethodByName(Type type)
{
var methods = type.GetMethods(AccessTools.allDeclared);
if (_byRef)
type = type.MakeByRefType();
MethodInfo? methodThatNeedsWrapper = null;
for (var i = 0; i < methods.Length; i++)
{
var method = methods[i];
if (method.Name is not ("Equals" or "op_Equality")
|| method.ReturnType != typeof(bool))
continue;
var parameters = method.GetParameters();
var isGood = (parameters.Length == 2 && method.IsStatic) || parameters.Length == 1;
for (var j = 0; j < parameters.Length; j++)
{
if (parameters[j].ParameterType != type)
isGood = false;
}
if (!isGood)
continue;
if (_byRef || method.IsStatic)
return method;
else
methodThatNeedsWrapper = method;
}
return methodThatNeedsWrapper != null ? CompileWrapperMethod(type, methodThatNeedsWrapper) : null;
}
protected MethodInfo CompileWrapperMethod(Type type, MethodInfo method)
{
#if DEBUG
Log.Message($"Method {method.FullDescription()} determined to be in need of wrapper");
#endif
return GetGeneric(nameof(FunctionPointers.Equals.Methods.ValueType), type);
// var dm = new DynamicMethod($"{_namePrefix}{type.Name}", typeof(bool),
// new[] { type, type }, type, true);
// var il = dm.GetILGenerator();
//
// il.Emit(FishTranspiler.ArgumentAddress(0));
// il.Emit(FishTranspiler.Argument(1));
// il.Emit(FishTranspiler.Call(method)); // throws NullRef somehow
// il.Emit(FishTranspiler.Return);
//
// return dm.CreateDelegate(typeof(Func<,,>).MakeGenericType(type, type, typeof(bool))).Method;
}
protected MethodInfo CompileEqualsMethod(Type type)
{
try
{
var dm = new DynamicMethod($"{_namePrefix}{type.Name}", typeof(bool),
_byRef ? new[] { type.MakeByRefType(), type.MakeByRefType() } : new[] { type, type }, type, true);
var il = dm.GetILGenerator();
var fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
var needsJump = fields.Length > 1;
var falseLabel = needsJump ? il.DefineLabel() : default;
var trueLabel = needsJump ? il.DefineLabel() : default;
for (var i = 0; i < fields.Length; i++)
{
var field = fields[i];
for (var j = 0; j < 2; j++)
{
il.Emit(FishTranspiler.Argument(j));
il.Emit(FishTranspiler.Field(field));
}
il.Emit(FishTranspiler.Call(GetForType(field.FieldType)));
if (needsJump)
{
il.Emit(i == fields.Length - 1
? FishTranspiler.GoTo_Short(trueLabel)
: FishTranspiler.IfFalse_Short(falseLabel));
}
}
if (needsJump)
{
il.MarkLabel(falseLabel);
il.Emit(FishTranspiler.Constant(0));
il.MarkLabel(trueLabel);
}
il.Emit(FishTranspiler.Return);
return dm.CreateDelegate(
(_byRef ? typeof(ByRefFunc<,,>) : typeof(Func<,,>)).MakeGenericType(type, type, typeof(bool)))
.Method;
}
catch (Exception e)
{
return LogErrorAndReturnFallback(type, e);
}
}
private MethodInfo LogErrorAndReturnFallback(Type type, Exception e)
{
Log.Error($"Failed compiling specialized Equals method for {
type.FullDescription()}. Returning fallback instead.\n{e}{new StackTrace()}");
return GetGeneric(nameof(FunctionPointers.Equals.Methods.ValueType), type);
}
private static MethodInfo[] GetMethodInfoArraySafely(Type type)
{
try
{
return GetMethodInfoArray(type, static m
=> m.ReturnType == typeof(bool)
&& m.GetParameters().Length == 2);
}
catch (Exception e)
{
Log.Error($"Exception while preparing method info array for type {type}:\n{e}");
throw;
}
}
public Equals(Type type, string dynamicMethodNamePrefix, bool byRef)
: base(GetMethodInfoArraySafely(type), dynamicMethodNamePrefix, byRef)
{
}
private delegate TResult ByRefFunc<T1, T2, out TResult>(ref T1 arg1, ref T2 arg2);
}
public class HashCode : MethodFactory
{
protected override MethodInfo GetOrMakeMethodForType(Type type)
=> GetSpecializedMethod(type)
?? (type.IsNullable(out var genericArgument)
&& genericArgument.IsAssignableTo(typeof(IEquatable<>), genericArgument)
? GetGeneric(nameof(FunctionPointers.GetHashCode.Methods.Nullable), genericArgument)
: type.IsEnum
? GetGeneric(Type.GetTypeCode(Enum.GetUnderlyingType(type))
switch
{
TypeCode.Int16 => nameof(FunctionPointers.GetHashCode.Methods.ShortEnum),
TypeCode.SByte => nameof(FunctionPointers.GetHashCode.Methods.SbyteEnum),
TypeCode.Byte
or TypeCode.UInt16
or TypeCode.Int32
or TypeCode.UInt32 => nameof(FunctionPointers.GetHashCode.Methods.Enum),
TypeCode.Int64
or TypeCode.UInt64 => nameof(FunctionPointers.GetHashCode.Methods.LongEnum),
_ => ThrowHelper.ThrowNotSupportedException<string>()
}, type)
: type.IsValueType
? TryGetMethodByName(type) ?? CompileHashCodeMethod(type)
: GetGeneric(nameof(FunctionPointers.GetHashCode.Methods.Object), type));
protected MethodInfo? TryGetMethodByName(Type type)
{
var methods = type.GetMethods(AccessTools.allDeclared);
if (_byRef)
type = type.MakeByRefType();
MethodInfo? methodThatNeedsWrapper = null;
for (var i = 0; i < methods.Length; i++)
{
var method = methods[i];
if (method.Name != "GetHashCode"
|| method.ReturnType != typeof(int))
continue;
var parameters = method.GetParameters();
var isGood = (parameters.Length == 1 && method.IsStatic) || parameters.Length == 0;
for (var j = 0; j < parameters.Length; j++)
{
if (parameters[j].ParameterType != type)
isGood = false;
}
if (!isGood)
continue;
if (_byRef || method.IsStatic)
return method;
else
methodThatNeedsWrapper = method;
}
return methodThatNeedsWrapper != null ? CompileWrapperMethod(type, methodThatNeedsWrapper) : null;
}
protected MethodInfo CompileWrapperMethod(Type type, MethodInfo method)
{
#if DEBUG
Log.Message($"Method {method.FullDescription()} determined to be in need of wrapper");
#endif
return GetGeneric(nameof(FunctionPointers.GetHashCode.Methods.ValueType), type);
// var dm = new DynamicMethod($"{_namePrefix}{type.Name}", typeof(int),
// new[] { type }, type, true);
// var il = dm.GetILGenerator();
//
// il.Emit(FishTranspiler.ArgumentAddress(0));
// il.Emit(FishTranspiler.Call(method));
// il.Emit(FishTranspiler.Return);
//
// return dm.CreateDelegate(typeof(HashCodeGetter<>).MakeGenericType(type)).Method;
}
private MethodInfo CompileHashCodeMethod(Type type)
{
try
{
var dm = new DynamicMethod($"{_namePrefix}{type.Name}", typeof(int),
_byRef ? new[] { type.MakeByRefType() } : new[] { type }, type, true);
var il = dm.GetILGenerator();
var fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
for (var i = 0; i < fields.Length; i++)
{
var field = fields[i];
il.Emit(FishTranspiler.Argument(0));
il.Emit(FishTranspiler.Field(field));
if (field.FieldType != typeof(int))
il.Emit(FishTranspiler.Call(GetForType(field.FieldType)));
if (i < 1)
continue;
il.Emit(FishTranspiler.Call<Func<int, int, int>>(i == 1
? global::FisheryLib.HashCode.Combine
: global::FisheryLib.HashCode.CombineWith));
}
il.Emit(FishTranspiler.Return);
return dm.CreateDelegate((_byRef ? typeof(ByRefHashCodeGetter<>) : typeof(HashCodeGetter<>))
.MakeGenericType(type)).Method;
}
catch (Exception e)
{
return LogErrorAndReturnFallback(type, e);
}
}
private MethodInfo LogErrorAndReturnFallback(Type type, Exception e)
{
Log.Error($"Failed compiling specialized GetHashCode method for {
type.FullDescription()}. Returning fallback instead.\n{e}{new StackTrace()}");
return GetGeneric(nameof(FunctionPointers.GetHashCode.Methods.ValueType), type);
}
private static MethodInfo[] GetMethodInfoArraySafely(Type type)
{
try
{
return GetMethodInfoArray(type, static m
=> m.ReturnType == typeof(int)
&& m.GetParameters().Length == 1);
}
catch (Exception e)
{
Log.Error($"Exception while preparing method info array for type {type}:\n{e}");
throw;
}
}
public HashCode(Type type, string dynamicMethodNamePrefix, bool byRef)
: base(GetMethodInfoArraySafely(type), dynamicMethodNamePrefix, byRef)
{
}
private delegate int ByRefHashCodeGetter<T>(ref T arg);
private delegate int HashCodeGetter<in T>(T arg);
}
}