Added FisheryLib.Collections.FishTable and Mono.Cecil extension methods

A bit of refactoring has been done too
This commit is contained in:
bbradson
2023-06-03 20:10:58 +02:00
parent 8fa92f15a6
commit cdf209eb02
72 changed files with 8145 additions and 2098 deletions
+200 -53
View File
@@ -3,16 +3,25 @@
// 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.Linq;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Security;
using FisheryLib.Collections;
namespace FisheryLib.FunctionPointers;
public static class Convert<TFrom> where TFrom : IConvertible
public static class Convert<TFrom>
{
public static class To<TTo>
{
public static readonly unsafe delegate*<TFrom, IFormatProvider, TTo> Default = Convert.GetDefaultConverter<TFrom, TTo>();
public static readonly unsafe delegate*<TFrom, IFormatProvider, TTo> Default
= Convert.GetDefaultConverter<TFrom, TTo>();
}
/// <summary>
/// Used when TFrom isn't a known sealed type, i.e. with just the IConvertible interface
/// Used when TFrom isn't a known sealed type, e.g. with just the IConvertible interface
/// </summary>
internal static class Fallback<TTo>
{
@@ -23,64 +32,202 @@ public static class Convert<TFrom> where TFrom : IConvertible
internal static class Convert
{
private static MethodInfo GetConverterMethod<TFrom>(Type to) where TFrom : IConvertible
private static class FromObjectTo<TTo>
{
var info = typeof(TFrom).GetMethod(GetConvertMethodName(to));
if (info.GetParameters().Length > 1)
private static FishTable<RuntimeTypeHandle, FuncWrapper> _delegates = new();
static FromObjectTo()
=> _delegates.EntryAdded += static pair =>
{
var from = Type.GetTypeFromHandle(pair.Key);
ref var entryRef = ref _delegates.GetReference(pair.Key);
var to = typeof(TTo);
entryRef.Func = CreateObjectConverter(from, to);
};
private static Func<object, IFormatProvider, TTo> CreateObjectConverter(Type from, Type to)
{
info = typeof(Convert)
.GetMethod(nameof(ConvertToType), AccessTools.allDeclared)
.MakeGenericMethod(new[] { typeof(TFrom), to });
try
{
var dm = new DynamicMethod($"ObjectConverter_{from}_{to}", to,
new[] { typeof(object), typeof(IFormatProvider) }, typeof(FromObjectTo<TTo>), true);
var il = dm.GetILGenerator();
il.Emit(FishTranspiler.This);
if (from.IsValueType)
{
il.Emit(FishTranspiler.Call(typeof(FromObjectTo<TTo>), nameof(UnboxSafely),
generics: new[] { from }));
}
il.Emit(FishTranspiler.Argument(1));
il.Emit(FishTranspiler.Call(GetConverterMethod(from, to)));
il.Emit(FishTranspiler.Return);
return (Func<object, IFormatProvider, TTo>)dm.CreateDelegate(typeof(Func<object, IFormatProvider, TTo>));
}
catch (Exception ex)
{
throw new($"Failed to compile object converter from {from.FullDescription()} to {
to.FullDescription()}\n{ex}");
}
}
return info;
public static TTo Method(object obj, IFormatProvider provider)
=> _delegates.GetOrAdd(obj.GetType().TypeHandle).Func(obj, provider);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[SecuritySafeCritical]
private static ref T UnboxSafely<T>(object obj) where T : struct
=> ref Unsafe.Unbox<T>(obj is T ? obj : default);
private record struct FuncWrapper
{
public Func<object, IFormatProvider, TTo> Func;
}
}
internal static Func<TFrom, IFormatProvider, TTo> GetDefaultConverterFunc<TFrom, TTo>() where TFrom : IConvertible
=> (Func<TFrom, IFormatProvider, TTo>)GetConverterMethod<TFrom>(typeof(TTo))
.CreateDelegate(typeof(Func<TFrom, IFormatProvider, TTo>));
private static MethodInfo GetConverterMethod<TFrom>(Type to) => GetConverterMethod(typeof(TFrom), to);
private static MethodInfo GetConverterMethod(Type from, Type to)
{
try
{
var info = _convertMethodsByType.TryGetValue(to, out var value)
&& TryFindInterfaceMethod(from, value) is { } methodByName
? methodByName
: TryFindMethodThroughReflection(to, from);
internal static unsafe delegate*<TFrom, IFormatProvider, TTo> GetDefaultConverter<TFrom, TTo>() where TFrom : IConvertible
=> !typeof(TFrom).IsValueType && (!typeof(TFrom).IsSealed || typeof(TFrom).IsInterface || typeof(TFrom).IsAbstract)
? &Convert<TFrom>.Fallback<TTo>.FallbackMethod
: (delegate*<TFrom, IFormatProvider, TTo>)GetConverterMethod<TFrom>(typeof(TTo))
.MethodHandle.GetFunctionPointer();
if (info is null)
ThrowFailedToFindConverterInvalidOperationException(from, to);
if (info.IsStatic)
{
if (info.GetParameters() is [_])
info = GenerateWrapperMethod(from, to, info);
}
else if (info.GetParameters().Length > 1)
{
info = typeof(Convert)
.GetMethod(nameof(ConvertToType), AccessTools.allDeclared)!
.MakeGenericMethod(from, to);
}
return info;
}
catch (Exception e)
{
Log.Error($"Exception thrown while trying to get converter method:\n{e}");
throw;
}
}
private static MethodInfo? TryFindInterfaceMethod(Type from, MethodInfo interfaceMethod)
{
if (!typeof(IConvertible).IsAssignableFrom(from))
return null;
var interfaceMap = from.GetInterfaceMap(typeof(IConvertible));
var index = Array.IndexOf(interfaceMap.InterfaceMethods, interfaceMethod);
return index < 0 ? null : interfaceMap.TargetMethods[index];
}
[DoesNotReturn]
private static MethodInfo ThrowFailedToFindConverterInvalidOperationException(Type from, Type to)
=> ThrowHelper.ThrowInvalidOperationException<MethodInfo>($"Failed to find converter from type '{
from.FullDescription()}' to type '{to.FullDescription()}'");
private static MethodInfo? TryFindMethodThroughReflection(Type to, Type from)
{
var parameters = new[] { from };
var fromMethods = from.GetMethods(BindingFlags.Public | BindingFlags.Static);
if (TryFindMethod(fromMethods, "op_Implicit", parameters, to) is { } info)
return info;
var toMethods = to.GetMethods(BindingFlags.Public | BindingFlags.Static);
return TryFindMethod(toMethods, "op_Implicit", parameters, from)
?? TryFindMethod(fromMethods, "op_Explicit", parameters, to)
?? TryFindMethod(toMethods, "op_Explicit", parameters, from)
?? (!from.IsValueType
? typeof(FromObjectTo<>).MakeGenericType(to).GetMethod(nameof(FromObjectTo<object>.Method))
: ThrowFailedToFindConverterInvalidOperationException(from, to));
}
private static MethodInfo GenerateWrapperMethod(Type from, Type to, MethodBase method)
{
var dm = new DynamicMethod($"Wrapper_op_{from.Name}_{to.Name}", to,
new[] { from.IsValueType ? from.MakeByRefType() : from, typeof(IFormatProvider) }, from, true);
var il = dm.GetILGenerator();
il.Emit(FishTranspiler.Argument(0));
if (from.IsValueType)
il.Emit(FishTranspiler.LoadIndirectly(from));
il.Emit(FishTranspiler.Call(method));
il.Emit(FishTranspiler.Return);
return dm.CreateDelegate(from.IsValueType
? typeof(RefConverterFunc<,>).MakeGenericType(from, to)
: typeof(Func<,,>).MakeGenericType(from, typeof(IFormatProvider), to)).Method;
}
private static MethodInfo? TryFindMethod(MethodInfo[] array, string name, Type[] parameters, Type returnType)
{
for (var i = array.Length; i-- > 0;)
{
var method = array[i];
if (method.Name != name || method.ReturnType != returnType)
continue;
var testedParameters = method.GetParameters();
if (testedParameters.Length != parameters.Length)
continue;
var parametersMatch = true;
for (var j = testedParameters.Length; j-- > 0;)
{
if (testedParameters[j].ParameterType == parameters[j])
continue;
parametersMatch = false;
break;
}
if (parametersMatch)
return method;
}
return null;
}
internal static Func<TFrom, IFormatProvider, TTo> GetDefaultConverterFunc<TFrom, TTo>()
=> (Func<TFrom, IFormatProvider, TTo>)GetConverterMethod<TFrom>(typeof(TTo))
.CreateDelegate(typeof(Func<TFrom, IFormatProvider, TTo>));
internal static unsafe delegate*<TFrom, IFormatProvider, TTo> GetDefaultConverter<TFrom, TTo>()
=> !typeof(TFrom).IsValueType
&& (!typeof(TFrom).IsSealed || typeof(TFrom).IsInterface || typeof(TFrom).IsAbstract)
? &Convert<TFrom>.Fallback<TTo>.FallbackMethod
: (delegate*<TFrom, IFormatProvider, TTo>)GetConverterMethod<TFrom>(typeof(TTo))
.MethodHandle.GetFunctionPointer();
private static TTo ConvertToType<TFrom, TTo>(TFrom from, IFormatProvider provider) where TFrom : IConvertible
=> (TTo)from.ToType(typeof(TTo), provider);
private static string GetConvertMethodName(Type type)
=> type == typeof(bool)
? nameof(IConvertible.ToBoolean)
: type == typeof(char)
? nameof(IConvertible.ToChar)
: type == typeof(sbyte)
? nameof(IConvertible.ToSByte)
: type == typeof(byte)
? nameof(IConvertible.ToByte)
: type == typeof(short)
? nameof(IConvertible.ToInt16)
: type == typeof(ushort)
? nameof(IConvertible.ToUInt16)
: type == typeof(int)
? nameof(IConvertible.ToInt32)
: type == typeof(uint)
? nameof(IConvertible.ToUInt32)
: type == typeof(long)
? nameof(IConvertible.ToInt64)
: type == typeof(ulong)
? nameof(IConvertible.ToUInt64)
: type == typeof(float)
? nameof(IConvertible.ToSingle)
: type == typeof(double)
? nameof(IConvertible.ToDouble)
: type == typeof(decimal)
? nameof(IConvertible.ToDecimal)
: type == typeof(DateTime)
? nameof(IConvertible.ToDateTime)
: type == typeof(string)
? nameof(IConvertible.ToString)
//: type == typeof(object)
//? value
: nameof(IConvertible.ToType);
}
private static readonly FishTable<Type, MethodInfo> _convertMethodsByType
= typeof(IConvertible).GetMethods()
.Where(static method
=> method.GetParameters() is [{ ParameterType: var type }] && type == typeof(IFormatProvider))
.ToFishTable(static info => info.ReturnType);
}
[SuppressMessage("ReSharper", "TypeParameterCanBeVariant")]
file delegate TReturn RefConverterFunc<TInstance, TReturn>(TInstance instance, IFormatProvider provider);
+54 -77
View File
@@ -3,118 +3,95 @@
// 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.Linq;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Security;
using JetBrains.Annotations;
namespace FisheryLib.FunctionPointers;
public static class Equals<T>
{
public static readonly unsafe delegate*<T, T, bool> Default = (delegate*<T, T, bool>)FunctionPointers.Equals.MakeDelegate(typeof(T));
public static readonly unsafe delegate*<T, T, bool> Default
= (delegate*<T, T, bool>)FunctionPointers.Equals.EqualsMethods.GetFunctionPointer(typeof(T));
}
public static class Equals
{
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
public static class Methods
{
public static bool Byte(byte x, byte y)
=> x == y;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Byte(byte x, byte y) => x == y;
public static bool Int(int x, int y)
=> x == y;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Int(int x, int y) => x == y;
public static bool Uint(uint x, uint y)
=> x == y;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Uint(uint x, uint y) => x == y;
public static bool Ushort(ushort x, ushort y)
=> x == y;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Ushort(ushort x, ushort y) => x == y;
public static bool IntPtr(IntPtr x, IntPtr y)
=> x == y;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IntPtr(IntPtr x, IntPtr y) => x == y;
public static bool String(string x, string y)
=> string.Equals(x, y);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool String(string x, string y) => string.Equals(x, y);
public static unsafe bool VoidPtr(void* x, void* y)
=> x == y;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe bool VoidPtr(void* x, void* y) => x == y;
public static bool RuntimeFieldHandle(RuntimeFieldHandle x, RuntimeFieldHandle y)
=> x.Value == y.Value;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[SecuritySafeCritical]
public static bool RuntimeFieldHandle(RuntimeFieldHandle x, RuntimeFieldHandle y) => x.Value == y.Value;
public static bool RuntimeTypeHandle(RuntimeTypeHandle x, RuntimeTypeHandle y)
=> x.Value == y.Value;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[SecuritySafeCritical]
public static bool RuntimeTypeHandle(RuntimeTypeHandle x, RuntimeTypeHandle y) => x.Value == y.Value;
public static bool RuntimeMethodHandle(RuntimeMethodHandle x, RuntimeMethodHandle y)
=> x.Value == y.Value;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[SecuritySafeCritical]
public static bool RuntimeMethodHandle(RuntimeMethodHandle x, RuntimeMethodHandle y) => x.Value == y.Value;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Label(Label x, Label y) => x.label == y.label;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Enum<T>(T x, T y) where T : struct
=> JitHelpers.UnsafeEnumCast(x) == JitHelpers.UnsafeEnumCast(y);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool LongEnum<T>(T x, T y) where T : struct
=> JitHelpers.UnsafeEnumCastLong(x) == JitHelpers.UnsafeEnumCastLong(y);
public static bool EquatableValueType<T>(T x, T y) where T : struct, IEquatable<T>
=> x.Equals(y);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool EquatableValueType<T>(T x, T y) where T : struct, IEquatable<T> => x.Equals(y);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Nullable<T>(T? x, T? y) where T : struct, IEquatable<T>
=> x.HasValue
? y.HasValue && x.Value.Equals(y.Value)
: !y.HasValue;
? y.HasValue && x.Value.Equals(y.Value)
: !y.HasValue;
public static bool EquatableReferenceType<T>(T x, T y) where T : IEquatable<T>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool EquatableReferenceType<T>(T? x, T? y) where T : class, IEquatable<T>
=> x != null
? y != null && x.Equals(y)
: y == null;
? y != null && x.Equals(y)
: y == null;
public static bool Reference<T>(T x, T y) where T : class
=> x == y;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Reference<T>(T x, T y) where T : class => x == y;
public static bool Object<T>(T x, T y)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Object<T>(T? x, T? y) where T : class
=> x != null
? y != null && x.Equals(y)
: y == null;
? y != null && x.Equals(y)
: y == null;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool ValueType<T>(T x, T y) where T : struct => x.Equals(y);
}
internal static unsafe IntPtr MakeDelegate(Type type)
=> _equalsMethods.Where(m => !m.IsGenericMethod)
.FirstOrDefault(m
=> m.GetParameters().First().ParameterType == type)
is { } specializedMethod
? specializedMethod.GetFunctionPointer()
: type.IsAssignableTo(typeof(IEquatable<>), type)
? GetMethod(
type.IsValueType
? nameof(Methods.EquatableValueType)
: nameof(Methods.EquatableReferenceType))
.MakeGenericMethod(type).GetFunctionPointer()
: type.IsNullable(out var genericArgument)
&& genericArgument.IsAssignableTo(typeof(IEquatable<>), genericArgument)
? GetMethod(nameof(Methods.Nullable)).MakeGenericMethod(genericArgument).GetFunctionPointer()
: GetMethod(
type.IsEnum
? Type.GetTypeCode(Enum.GetUnderlyingType(type))
switch
{
TypeCode.Int16
or TypeCode.SByte
or TypeCode.Byte
or TypeCode.UInt16
or TypeCode.Int32
or TypeCode.UInt32 => nameof(Methods.Enum),
TypeCode.Int64
or TypeCode.UInt64 => nameof(Methods.LongEnum),
_ => ThrowHelper.ThrowNotSupportedException<string>()
}
: nameof(Methods.Object)
).MakeGenericMethod(type).GetFunctionPointer();
private static MethodInfo GetMethod(string name)
=> Array.Find(_equalsMethods, m => m.Name == name);
private static MethodInfo[] _equalsMethods
= typeof(Methods)
.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.DeclaredOnly)
.Where(m
=> m.ReturnType == typeof(bool)
&& m.GetParameters().Length == 2)
.ToArray();
public static readonly MethodFactory.Equals EqualsMethods = new(typeof(Methods), "FisheryEqualsMethod_", false);
}
@@ -3,118 +3,96 @@
// 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.Linq;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Security;
using JetBrains.Annotations;
namespace FisheryLib.FunctionPointers;
public static class EqualsByRef<T>
{
public static readonly unsafe delegate*<ref T, ref T, bool> Default = (delegate*<ref T, ref T, bool>)EqualsByRef.MakeDelegate(typeof(T));
public static readonly unsafe delegate*<ref T, ref T, bool> Default
= (delegate*<ref T, ref T, bool>)EqualsByRef.EqualsMethods.GetFunctionPointer(typeof(T));
}
public static class EqualsByRef
{
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
public static class Methods
{
public static bool Byte(ref byte x, ref byte y)
=> x == y;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Byte(ref byte x, ref byte y) => x == y;
public static bool Int(ref int x, ref int y)
=> x == y;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Int(ref int x, ref int y) => x == y;
public static bool Uint(ref uint x, ref uint y)
=> x == y;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Uint(ref uint x, ref uint y) => x == y;
public static bool Ushort(ref ushort x, ref ushort y)
=> x == y;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Ushort(ref ushort x, ref ushort y) => x == y;
public static bool IntPtr(ref IntPtr x, ref IntPtr y)
=> x == y;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IntPtr(ref IntPtr x, ref IntPtr y) => x == y;
public static bool String(ref string x, ref string y)
=> string.Equals(x, y);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool String(ref string x, ref string y) => string.Equals(x, y);
public static unsafe bool VoidPtr(ref void* x, ref void* y)
=> x == y;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe bool VoidPtr(ref void* x, ref void* y) => x == y;
public static bool RuntimeFieldHandle(ref RuntimeFieldHandle x, ref RuntimeFieldHandle y)
=> x.Value == y.Value;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[SecuritySafeCritical]
public static bool RuntimeFieldHandle(ref RuntimeFieldHandle x, ref RuntimeFieldHandle y) => x.Value == y.Value;
public static bool RuntimeTypeHandle(ref RuntimeTypeHandle x, ref RuntimeTypeHandle y)
=> x.Value == y.Value;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[SecuritySafeCritical]
public static bool RuntimeTypeHandle(ref RuntimeTypeHandle x, ref RuntimeTypeHandle y) => x.Value == y.Value;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[SecuritySafeCritical]
public static bool RuntimeMethodHandle(ref RuntimeMethodHandle x, ref RuntimeMethodHandle y)
=> x.Value == y.Value;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Label(ref Label x, ref Label y) => x.label == y.label;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Enum<T>(ref T x, ref T y) where T : struct
=> JitHelpers.UnsafeEnumCast(x) == JitHelpers.UnsafeEnumCast(y);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool LongEnum<T>(ref T x, ref T y) where T : struct
=> JitHelpers.UnsafeEnumCastLong(x) == JitHelpers.UnsafeEnumCastLong(y);
public static bool EquatableValueType<T>(ref T x, ref T y) where T : struct, IEquatable<T>
=> x.Equals(y);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool EquatableValueType<T>(ref T x, ref T y) where T : struct, IEquatable<T> => x.Equals(y);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Nullable<T>(ref T? x, ref T? y) where T : struct, IEquatable<T>
=> x.HasValue
? y.HasValue && x.Value.Equals(y.Value)
: !y.HasValue;
? y.HasValue && x.Value.Equals(y.Value)
: !y.HasValue;
public static bool EquatableReferenceType<T>(ref T x, ref T y) where T : IEquatable<T>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool EquatableReferenceType<T>(ref T? x, ref T? y) where T : class, IEquatable<T>
=> x != null
? y != null && x.Equals(y)
: y == null;
? y != null && x.Equals(y)
: y == null;
public static bool Reference<T>(ref T x, ref T y) where T : class
=> x == y;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Reference<T>(ref T x, ref T y) where T : class => x == y;
public static bool Object<T>(ref T x, ref T y)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Object<T>(ref T? x, ref T? y) where T : class
=> x != null
? y != null && x.Equals(y)
: y == null;
? y != null && x.Equals(y)
: y == null;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool ValueType<T>(ref T x, ref T y) where T : struct => x.Equals(y);
}
internal static unsafe IntPtr MakeDelegate(Type type)
=> _equalsMethods.Where(m => !m.IsGenericMethod)
.FirstOrDefault(m
=> m.GetParameters().First().ParameterType.GetElementType() == type)
is { } specializedMethod
? specializedMethod.GetFunctionPointer()
: type.IsAssignableTo(typeof(IEquatable<>), type)
? GetMethod(
type.IsValueType
? nameof(Methods.EquatableValueType)
: nameof(Methods.EquatableReferenceType))
.MakeGenericMethod(type).GetFunctionPointer()
: type.IsNullable(out var genericArgument)
&& genericArgument.IsAssignableTo(typeof(IEquatable<>), genericArgument)
? GetMethod(nameof(Methods.Nullable)).MakeGenericMethod(genericArgument).GetFunctionPointer()
: GetMethod(
type.IsEnum
? Type.GetTypeCode(Enum.GetUnderlyingType(type))
switch
{
TypeCode.Int16
or TypeCode.SByte
or TypeCode.Byte
or TypeCode.UInt16
or TypeCode.Int32
or TypeCode.UInt32 => nameof(Methods.Enum),
TypeCode.Int64
or TypeCode.UInt64 => nameof(Methods.LongEnum),
_ => ThrowHelper.ThrowNotSupportedException<string>()
}
: nameof(Methods.Object)
).MakeGenericMethod(type).GetFunctionPointer();
private static MethodInfo GetMethod(string name)
=> Array.Find(_equalsMethods, m => m.Name == name);
private static MethodInfo[] _equalsMethods
= typeof(Methods)
.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.DeclaredOnly)
.Where(m
=> m.ReturnType == typeof(bool)
&& m.GetParameters().Length == 2)
.ToArray();
public static readonly MethodFactory.Equals EqualsMethods = new(typeof(Methods), "FisheryEqualsByRefMethod_", true);
}
@@ -3,104 +3,50 @@
// 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.Linq;
using System.Runtime.CompilerServices;
using JetBrains.Annotations;
namespace FisheryLib.FunctionPointers;
public static class GetHashCode<T>
{
public static readonly unsafe delegate*<T, int> Default = (delegate*<T, int>)FunctionPointers.GetHashCode.MakeDelegate(typeof(T));
public static readonly unsafe delegate*<T, int> Default
= (delegate*<T, int>)FunctionPointers.GetHashCode.HashCodeMethods.GetFunctionPointer(typeof(T));
}
public static class GetHashCode
{
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
public static class Methods
{
public static int Byte(byte obj)
=> obj;
public static int Byte(byte obj) => obj;
public static int Int(int obj)
=> obj;
public static int Int(int obj) => obj;
public static int Uint(uint obj)
=> (int)obj;
public static int Uint(uint obj) => (int)obj;
public static int Ushort(ushort obj)
=> obj;
public static int Ushort(ushort obj) => obj;
public static unsafe int VoidPtr(void* obj)
=> (int)obj;
public static unsafe int VoidPtr(void* obj) => (int)obj;
public static int Enum<T>(T obj) where T : struct
=> JitHelpers.UnsafeEnumCast(obj).GetHashCode();
public static int Enum<T>(T obj) where T : struct => JitHelpers.UnsafeEnumCast(obj).GetHashCode();
public static int SbyteEnum<T>(T obj) where T : struct
=> ((sbyte)JitHelpers.UnsafeEnumCast(obj)).GetHashCode();
public static int SbyteEnum<T>(T obj) where T : struct => ((sbyte)JitHelpers.UnsafeEnumCast(obj)).GetHashCode();
public static int ShortEnum<T>(T obj) where T : struct
=> ((short)JitHelpers.UnsafeEnumCast(obj)).GetHashCode();
public static int ShortEnum<T>(T obj) where T : struct => ((short)JitHelpers.UnsafeEnumCast(obj)).GetHashCode();
public static int LongEnum<T>(T obj) where T : struct
=> JitHelpers.UnsafeEnumCastLong(obj).GetHashCode();
public static int LongEnum<T>(T obj) where T : struct => JitHelpers.UnsafeEnumCastLong(obj).GetHashCode();
public static int ValueType<T>(T obj) where T : struct
=> obj.GetHashCode();
public static int ValueType<T>(T obj) where T : struct => obj.GetHashCode();
public static int Nullable<T>(T? obj) where T : struct
=> obj.GetHashCode();
public static unsafe int Nullable<T>(T? obj) where T : struct
=> !obj.HasValue ? 0 : GetHashCode<T>.Default(obj.GetValueOrDefault());
public static int Reference<T>(T obj) where T : class
=> RuntimeHelpers.GetHashCode(obj);
public static int Reference<T>(T obj) where T : class => RuntimeHelpers.GetHashCode(obj);
public static int Object<T>(T obj)
=> obj?.GetHashCode() ?? 0;
public static int Object<T>(T? obj) where T : class => obj?.GetHashCode() ?? 0;
}
internal static unsafe IntPtr MakeDelegate(Type type)
=> _getHashCodeMethods.Where(m => !m.IsGenericMethod)
.FirstOrDefault(m
=> m.GetParameters().First().ParameterType == type)
is { } specializedMethod
? specializedMethod.GetFunctionPointer()
: type.IsNullable(out var genericArgument)
&& genericArgument.IsAssignableTo(typeof(IEquatable<>), genericArgument)
? GetMethod(nameof(Methods.Nullable)).MakeGenericMethod(genericArgument).GetFunctionPointer()
: GetMethod(
type.IsEnum
? Type.GetTypeCode(Enum.GetUnderlyingType(type))
switch
{
TypeCode.Int16 => nameof(Methods.ShortEnum),
TypeCode.SByte => nameof(Methods.SbyteEnum),
TypeCode.Byte
or TypeCode.UInt16
or TypeCode.Int32
or TypeCode.UInt32 => nameof(Methods.Enum),
TypeCode.Int64
or TypeCode.UInt64 => nameof(Methods.LongEnum),
_ => ThrowHelper.ThrowNotSupportedException<string>()
}
: type.IsValueType
? nameof(Methods.ValueType)
: nameof(Methods.Object)
).MakeGenericMethod(type).GetFunctionPointer();
private static MethodInfo GetMethod(string name)
=> Array.Find(_getHashCodeMethods, m => m.Name == name);
private static MethodInfo[] _getHashCodeMethods
= typeof(Methods)
.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.DeclaredOnly)
.Where(m
=> m.ReturnType == typeof(int)
&& m.GetParameters().Length == 1)
.ToArray();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe delegate*<V, int> Cast<T,V>(delegate*<T, int> @delegate)
=> (delegate*<V, int>)@delegate;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe delegate*<T, int> CastVoid<T>(delegate*<void*, int> @delegate)
=> (delegate*<T, int>)@delegate;
public static readonly MethodFactory.HashCode HashCodeMethods
= new(typeof(Methods), "FisheryHashCodeMethod_", false);
}
@@ -3,36 +3,33 @@
// 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.Linq;
using System.Runtime.CompilerServices;
using JetBrains.Annotations;
namespace FisheryLib.FunctionPointers;
public static class GetHashCodeByRef<T>
{
public static readonly unsafe delegate*<ref T, int> Default = (delegate*<ref T, int>)GetHashCodeByRef.MakeDelegate(typeof(T));
public static readonly unsafe delegate*<ref T, int> Default
= (delegate*<ref T, int>)GetHashCodeByRef.HashCodeMethods.GetFunctionPointer(typeof(T));
}
public static class GetHashCodeByRef
{
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
public static class Methods
{
public static int Byte(ref byte obj)
=> obj;
public static int Byte(ref byte obj) => obj;
public static int Int(ref int obj)
=> obj;
public static int Int(ref int obj) => obj;
public static int Uint(ref uint obj)
=> (int)obj;
public static int Uint(ref uint obj) => (int)obj;
public static int Ushort(ref ushort obj)
=> obj;
public static int Ushort(ref ushort obj) => obj;
public static unsafe int VoidPtr(ref void* obj)
=> (int)obj;
public static unsafe int VoidPtr(ref void* obj) => (int)obj;
public static int Enum<T>(ref T obj) where T : struct
=> JitHelpers.UnsafeEnumCast(obj).GetHashCode();
public static int Enum<T>(ref T obj) where T : struct => JitHelpers.UnsafeEnumCast(obj).GetHashCode();
public static int SbyteEnum<T>(ref T obj) where T : struct
=> ((sbyte)JitHelpers.UnsafeEnumCast(obj)).GetHashCode();
@@ -40,59 +37,17 @@ public static class GetHashCodeByRef
public static int ShortEnum<T>(ref T obj) where T : struct
=> ((short)JitHelpers.UnsafeEnumCast(obj)).GetHashCode();
public static int LongEnum<T>(ref T obj) where T : struct
=> JitHelpers.UnsafeEnumCastLong(obj).GetHashCode();
public static int LongEnum<T>(ref T obj) where T : struct => JitHelpers.UnsafeEnumCastLong(obj).GetHashCode();
public static int ValueType<T>(ref T obj) where T : struct
=> obj.GetHashCode();
public static int ValueType<T>(ref T obj) where T : struct => obj.GetHashCode();
public static int Nullable<T>(ref T? obj) where T : struct
=> obj.GetHashCode();
public static int Nullable<T>(ref T? obj) where T : struct => obj.GetHashCode();
public static int Reference<T>(ref T obj) where T : class
=> RuntimeHelpers.GetHashCode(obj);
public static int Reference<T>(ref T obj) where T : class => RuntimeHelpers.GetHashCode(obj);
public static int Object<T>(ref T obj)
=> obj?.GetHashCode() ?? 0;
public static int Object<T>(ref T? obj) where T : class => obj?.GetHashCode() ?? 0;
}
internal static unsafe IntPtr MakeDelegate(Type type)
=> _getHashCodeMethods.Where(m => !m.IsGenericMethod)
.FirstOrDefault(m
=> m.GetParameters().First().ParameterType.GetElementType() == type)
is { } specializedMethod
? specializedMethod.GetFunctionPointer()
: type.IsNullable(out var genericArgument)
&& genericArgument.IsAssignableTo(typeof(IEquatable<>), genericArgument)
? GetMethod(nameof(Methods.Nullable)).MakeGenericMethod(genericArgument).GetFunctionPointer()
: GetMethod(
type.IsEnum
? Type.GetTypeCode(Enum.GetUnderlyingType(type))
switch
{
TypeCode.Int16 => nameof(Methods.ShortEnum),
TypeCode.SByte => nameof(Methods.SbyteEnum),
TypeCode.Byte
or TypeCode.UInt16
or TypeCode.Int32
or TypeCode.UInt32 => nameof(Methods.Enum),
TypeCode.Int64
or TypeCode.UInt64 => nameof(Methods.LongEnum),
_ => ThrowHelper.ThrowNotSupportedException<string>()
}
: type.IsValueType
? nameof(Methods.ValueType)
: nameof(Methods.Object)
).MakeGenericMethod(type).GetFunctionPointer();
private static MethodInfo GetMethod(string name)
=> Array.Find(_getHashCodeMethods, m => m.Name == name);
private static MethodInfo[] _getHashCodeMethods
= typeof(Methods)
.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.DeclaredOnly)
.Where(m
=> m.ReturnType == typeof(int)
&& m.GetParameters().Length == 1)
.ToArray();
public static readonly MethodFactory.HashCode HashCodeMethods
= new(typeof(Methods), "FisheryHashCodeByRefMethod_", true);
}
@@ -0,0 +1,393 @@
// 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);
}
}