BizHawk/src/BizHawk.Common/FPCtrl.cs

65 lines
2.5 KiB
C#

using System.IO;
using System.Runtime.InteropServices;
namespace BizHawk.Common
{
// .NET is weird and sets the x87 control register to double precision
// It does this even on Linux which normally expects it set to extended x87 precision
// Even stranger is that this appears to be completely unneeded on x86-64
// On Windows, x87 registers are prohibited to be used in kernel code, and
// MSVC will not use the x87 registers (and presumably this extends to userland OS code)
// .NET presumbly follows MSVC and would not use the x87 registers (why would it? SSE is available! long double doesn't exist!)
// This thus only screws over code compiled with MinGW (which use extended x87 precision for long double)
// Or screws over any unmanaged code on Linux, which doubly includes all waterbox cores (!!!)
// Of course in practice, this only applies to any code using long double, which is rarely used typically
// But musl (used for waterbox cores) does end up using it for float formating in the printf family
// This can extend to issues in games: https://github.com/TASEmulators/BizHawk/issues/3726
public static class FPCtrl
{
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void FixFPCtrlDelegate();
private static readonly MemoryBlock? _memory;
private static readonly FixFPCtrlDelegate? _fixFpCtrl;
static FPCtrl()
{
// not usable outside of x64
if (RuntimeInformation.ProcessArchitecture != Architecture.X64)
{
return;
}
// generate assembly for fixing the x87 control register
_memory = new(4096);
_memory.Protect(_memory.Start, _memory.Size, MemoryBlock.Protection.RW);
var ss = _memory.GetStream(_memory.Start, 64, true);
var bw = new BinaryWriter(ss);
// FYI: The push/pop is only needed on Windows, but doesn't do any harm on Linux
bw.Write((byte)0x50); // push rax
bw.Write(0x06247CD9); // fnstcw word[rsp + 6]
bw.Write(0x07244C80); // or byte[rsp + 7], 3
bw.Write((byte)0x03);
bw.Write(0x06246CD9); // fldcw word[rsp + 6]
bw.Write((byte)0x58); // pop rax
bw.Write((byte)0xC3); // ret
_memory.Protect(_memory.Start, _memory.Size, MemoryBlock.Protection.RX);
_fixFpCtrl = Marshal.GetDelegateForFunctionPointer<FixFPCtrlDelegate>((IntPtr)_memory.Start);
}
public static void FixFPCtrl()
{
// not usable outside of x64
if (RuntimeInformation.ProcessArchitecture != Architecture.X64)
{
return;
}
_fixFpCtrl!();
}
}
}