From a18ea12bc08fb32683058d0583bdc984fed5ff8b Mon Sep 17 00:00:00 2001 From: nattthebear Date: Sun, 16 May 2021 10:03:03 -0400 Subject: [PATCH] Bsnes new - delegate refactoring Fix reliance on GetFields() ordering which, per MDN, is unspecified. CallingConventionAdapters.MakeWaterbox(...) doesn't care what order they're in, but snes_set_callbacks(...) does. --- src/BizHawk.BizInvoke/BizInvoker.cs | 2 +- src/BizHawk.BizInvoke/BizInvokerUtilities.cs | 49 +++++++++++++++---- .../Consoles/Nintendo/BSNES/BsnesApi.cs | 28 ++++++++--- .../Consoles/Nintendo/BSNES/BsnesCore.cs | 11 +---- 4 files changed, 62 insertions(+), 28 deletions(-) diff --git a/src/BizHawk.BizInvoke/BizInvoker.cs b/src/BizHawk.BizInvoke/BizInvoker.cs index e40b40ae1f..f37d633e01 100644 --- a/src/BizHawk.BizInvoke/BizInvoker.cs +++ b/src/BizHawk.BizInvoke/BizInvoker.cs @@ -81,7 +81,7 @@ namespace BizHawk.BizInvoke var aname = new AssemblyName("BizInvokeProxyAssembly"); ImplAssemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(aname, AssemblyBuilderAccess.Run); ImplModuleBuilder = ImplAssemblyBuilder.DefineDynamicModule("BizInvokerModule"); - ClassFieldOffset = BizInvokerUtilities.ComputeClassFieldOffset(); + ClassFieldOffset = BizInvokerUtilities.ComputeClassFirstFieldOffset(); StringOffset = BizInvokerUtilities.ComputeStringOffset(); } diff --git a/src/BizHawk.BizInvoke/BizInvokerUtilities.cs b/src/BizHawk.BizInvoke/BizInvokerUtilities.cs index 4c12c057f9..0e048e9b77 100644 --- a/src/BizHawk.BizInvoke/BizInvokerUtilities.cs +++ b/src/BizHawk.BizInvoke/BizInvokerUtilities.cs @@ -1,5 +1,8 @@ using System; +using System.Reflection; +using System.Reflection.Emit; using System.Runtime.InteropServices; +using System.Runtime.Serialization; namespace BizHawk.BizInvoke { @@ -44,21 +47,18 @@ namespace BizHawk.BizInvoke } /// - /// Didn't I have code somewhere else to do this already? + /// Computes the byte offset of the first field of any class relative to a class pointer. /// /// - public static int ComputeClassFieldOffset() + public static int ComputeClassFirstFieldOffset() { - var c = new CF(); - int ret; - fixed(int* fx = &c.FirstField) - { - U u = new(new U2(c)); - ret = (int) ((ulong) (UIntPtr) fx - (ulong) u.First!.P); - } - return ret; + return ComputeFieldOffset(typeof(CF).GetField("FirstField")); } + /// + /// Compute the byte offset of the first byte of string data (UTF16) relative to a pointer to the string. + /// + /// public static int ComputeStringOffset() { var s = new string(new char[0]); @@ -70,5 +70,34 @@ namespace BizHawk.BizInvoke } return ret; } + + /// + /// Compute the byte offset of a field relative to a pointer to the class instance. + /// Slow, so cache it if you need it. + /// + public static int ComputeFieldOffset(FieldInfo fi) + { + if (fi.DeclaringType.IsValueType) + { + throw new NotImplementedException("Only supported for class fields right now"); + } + + var obj = FormatterServices.GetUninitializedObject(fi.DeclaringType); + var method = new DynamicMethod("ComputeFieldOffsetHelper", typeof(int), new[] { typeof(object) }, typeof(string).Module, true); + var il = method.GetILGenerator(); + var local = il.DeclareLocal(fi.DeclaringType, true); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Stloc, local); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldflda, fi); + il.Emit(OpCodes.Conv_I); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Conv_I); + il.Emit(OpCodes.Sub); + il.Emit(OpCodes.Conv_I4); + il.Emit(OpCodes.Ret); + var del = (Func)method.CreateDelegate(typeof(Func)); + return del(obj); + } } } diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesApi.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesApi.cs index bbff8afc29..d40efde9fc 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesApi.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesApi.cs @@ -7,6 +7,7 @@ using BizHawk.Common; using BizHawk.Emulation.Cores.Waterbox; using BizHawk.BizInvoke; using BizHawk.Emulation.Common; +using System.Linq; namespace BizHawk.Emulation.Cores.Nintendo.BSNES { @@ -92,12 +93,10 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES public void SetCallbacks(SnesCallbacks callbacks) { - FieldInfo[] fieldInfos = typeof(SnesCallbacks).GetFields(); - IntPtr[] functionPointerArray = new IntPtr[fieldInfos.Length]; - for (int i = 0; i < fieldInfos.Length; i++) - { - functionPointerArray[i] = _adapter.GetFunctionPointerForDelegate((Delegate) fieldInfos[i].GetValue(callbacks)); - } + var functionPointerArray = callbacks + .AllDelegatesInMemoryOrder() + .Select(f => _adapter.GetFunctionPointerForDelegate(f)) + .ToArray(); core.snes_set_callbacks(functionPointerArray); } @@ -167,6 +166,21 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES public snes_audio_sample_t audioSampleCb; public snes_path_request_t pathRequestCb; public snes_trace_t snesTraceCb; + + private static List FieldsInOrder = null; + + public IEnumerable AllDelegatesInMemoryOrder() + { + if (FieldsInOrder == null) + { + FieldsInOrder = GetType() + .GetFields() + .OrderBy(fi => BizInvokerUtilities.ComputeFieldOffset(fi)) + .ToList(); + } + return FieldsInOrder + .Select(f => (Delegate)f.GetValue(this)); + } } public void Seal() @@ -186,7 +200,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES public void SaveStateBinary(BinaryWriter writer) { // if (serializedSize == 0) - // serializedSize = _core.snes_serialized_size(); + // serializedSize = _core.snes_serialized_size(); // TODO: do some profiling and testing to check whether this is actually better than _exe.SaveStateBinary(writer); // re-adding bsnes's own serialization will need to be done once it's confirmed to be deterministic, aka after libco update diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.cs index fcae5ce778..9faea9845f 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.cs @@ -59,16 +59,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES snesTraceCb = snes_trace }; - Api = new BsnesApi(CoreComm.CoreFileProvider.DllPath(), CoreComm, new Delegate[] - { - callbacks.inputPollCb, - callbacks.inputStateCb, - callbacks.noLagCb, - callbacks.videoFrameCb, - callbacks.audioSampleCb, - callbacks.pathRequestCb, - callbacks.snesTraceCb - }); + Api = new BsnesApi(CoreComm.CoreFileProvider.DllPath(), CoreComm, callbacks.AllDelegatesInMemoryOrder()); _controllers = new BsnesControllers(_syncSettings);