From a731ab3a2aad56e6ceb8b4e2444a61353246295c Mon Sep 17 00:00:00 2001 From: gdkchan <gab.dark.100@gmail.com> Date: Thu, 8 Aug 2019 15:56:22 -0300 Subject: Add a new JIT compiler for CPU code (#693) * Start of the ARMeilleure project * Refactoring around the old IRAdapter, now renamed to PreAllocator * Optimize the LowestBitSet method * Add CLZ support and fix CLS implementation * Add missing Equals and GetHashCode overrides on some structs, misc small tweaks * Implement the ByteSwap IR instruction, and some refactoring on the assembler * Implement the DivideUI IR instruction and fix 64-bits IDIV * Correct constant operand type on CSINC * Move division instructions implementation to InstEmitDiv * Fix destination type for the ConditionalSelect IR instruction * Implement UMULH and SMULH, with new IR instructions * Fix some issues with shift instructions * Fix constant types for BFM instructions * Fix up new tests using the new V128 struct * Update tests * Move DIV tests to a separate file * Add support for calls, and some instructions that depends on them * Start adding support for SIMD & FP types, along with some of the related ARM instructions * Fix some typos and the divide instruction with FP operands * Fix wrong method call on Clz_V * Implement ARM FP & SIMD move instructions, Saddlv_V, and misc. fixes * Implement SIMD logical instructions and more misc. fixes * Fix PSRAD x86 instruction encoding, TRN, UABD and UABDL implementations * Implement float conversion instruction, merge in LDj3SNuD fixes, and some other misc. fixes * Implement SIMD shift instruction and fix Dup_V * Add SCVTF and UCVTF (vector, fixed-point) variants to the opcode table * Fix check with tolerance on tester * Implement FP & SIMD comparison instructions, and some fixes * Update FCVT (Scalar) encoding on the table to support the Half-float variants * Support passing V128 structs, some cleanup on the register allocator, merge LDj3SNuD fixes * Use old memory access methods, made a start on SIMD memory insts support, some fixes * Fix float constant passed to functions, save and restore non-volatile XMM registers, other fixes * Fix arguments count with struct return values, other fixes * More instructions * Misc. fixes and integrate LDj3SNuD fixes * Update tests * Add a faster linear scan allocator, unwinding support on windows, and other changes * Update Ryujinx.HLE * Update Ryujinx.Graphics * Fix V128 return pointer passing, RCX is clobbered * Update Ryujinx.Tests * Update ITimeZoneService * Stop using GetFunctionPointer as that can't be called from native code, misc. fixes and tweaks * Use generic GetFunctionPointerForDelegate method and other tweaks * Some refactoring on the code generator, assert on invalid operations and use a separate enum for intrinsics * Remove some unused code on the assembler * Fix REX.W prefix regression on float conversion instructions, add some sort of profiler * Add hardware capability detection * Fix regression on Sha1h and revert Fcm** changes * Add SSE2-only paths on vector extract and insert, some refactoring on the pre-allocator * Fix silly mistake introduced on last commit on CpuId * Generate inline stack probes when the stack allocation is too large * Initial support for the System-V ABI * Support multiple destination operands * Fix SSE2 VectorInsert8 path, and other fixes * Change placement of XMM callee save and restore code to match other compilers * Rename Dest to Destination and Inst to Instruction * Fix a regression related to calls and the V128 type * Add an extra space on comments to match code style * Some refactoring * Fix vector insert FP32 SSE2 path * Port over the ARM32 instructions * Avoid memory protection races on JIT Cache * Another fix on VectorInsert FP32 (thanks to LDj3SNuD * Float operands don't need to use the same register when VEX is supported * Add a new register allocator, higher quality code for hot code (tier up), and other tweaks * Some nits, small improvements on the pre allocator * CpuThreadState is gone * Allow changing CPU emulators with a config entry * Add runtime identifiers on the ARMeilleure project * Allow switching between CPUs through a config entry (pt. 2) * Change win10-x64 to win-x64 on projects * Update the Ryujinx project to use ARMeilleure * Ensure that the selected register is valid on the hybrid allocator * Allow exiting on returns to 0 (should fix test regression) * Remove register assignments for most used variables on the hybrid allocator * Do not use fixed registers as spill temp * Add missing namespace and remove unneeded using * Address PR feedback * Fix types, etc * Enable AssumeStrictAbiCompliance by default * Ensure that Spill and Fill don't load or store any more than necessary --- ARMeilleure/State/Aarch32Mode.cs | 15 ++ ARMeilleure/State/ExecutionContext.cs | 130 +++++++++++++++++ ARMeilleure/State/ExecutionMode.cs | 9 ++ ARMeilleure/State/FPCR.cs | 23 +++ ARMeilleure/State/FPException.cs | 12 ++ ARMeilleure/State/FPRoundingMode.cs | 10 ++ ARMeilleure/State/FPSR.cs | 11 ++ ARMeilleure/State/FPType.cs | 11 ++ ARMeilleure/State/IExecutionContext.cs | 37 +++++ ARMeilleure/State/InstExceptionEventArgs.cs | 16 +++ ARMeilleure/State/InstUndefinedEventArgs.cs | 16 +++ ARMeilleure/State/NativeContext.cs | 157 ++++++++++++++++++++ ARMeilleure/State/PState.cs | 16 +++ ARMeilleure/State/RegisterAlias.cs | 41 ++++++ ARMeilleure/State/RegisterConsts.cs | 13 ++ ARMeilleure/State/V128.cs | 214 ++++++++++++++++++++++++++++ 16 files changed, 731 insertions(+) create mode 100644 ARMeilleure/State/Aarch32Mode.cs create mode 100644 ARMeilleure/State/ExecutionContext.cs create mode 100644 ARMeilleure/State/ExecutionMode.cs create mode 100644 ARMeilleure/State/FPCR.cs create mode 100644 ARMeilleure/State/FPException.cs create mode 100644 ARMeilleure/State/FPRoundingMode.cs create mode 100644 ARMeilleure/State/FPSR.cs create mode 100644 ARMeilleure/State/FPType.cs create mode 100644 ARMeilleure/State/IExecutionContext.cs create mode 100644 ARMeilleure/State/InstExceptionEventArgs.cs create mode 100644 ARMeilleure/State/InstUndefinedEventArgs.cs create mode 100644 ARMeilleure/State/NativeContext.cs create mode 100644 ARMeilleure/State/PState.cs create mode 100644 ARMeilleure/State/RegisterAlias.cs create mode 100644 ARMeilleure/State/RegisterConsts.cs create mode 100644 ARMeilleure/State/V128.cs (limited to 'ARMeilleure/State') diff --git a/ARMeilleure/State/Aarch32Mode.cs b/ARMeilleure/State/Aarch32Mode.cs new file mode 100644 index 00000000..395e288a --- /dev/null +++ b/ARMeilleure/State/Aarch32Mode.cs @@ -0,0 +1,15 @@ +namespace ARMeilleure.State +{ + enum Aarch32Mode + { + User = 0b10000, + Fiq = 0b10001, + Irq = 0b10010, + Supervisor = 0b10011, + Monitor = 0b10110, + Abort = 0b10111, + Hypervisor = 0b11010, + Undefined = 0b11011, + System = 0b11111 + } +} \ No newline at end of file diff --git a/ARMeilleure/State/ExecutionContext.cs b/ARMeilleure/State/ExecutionContext.cs new file mode 100644 index 00000000..22cfcb69 --- /dev/null +++ b/ARMeilleure/State/ExecutionContext.cs @@ -0,0 +1,130 @@ +using System; +using System.Diagnostics; + +namespace ARMeilleure.State +{ + public class ExecutionContext : IExecutionContext + { + private const int MinCountForCheck = 40000; + + private NativeContext _nativeContext; + + internal IntPtr NativeContextPtr => _nativeContext.BasePtr; + + private bool _interrupted; + + private static Stopwatch _tickCounter; + + private static double _hostTickFreq; + + public uint CtrEl0 => 0x8444c004; + public uint DczidEl0 => 0x00000004; + + public ulong CntfrqEl0 { get; set; } + public ulong CntpctEl0 + { + get + { + double ticks = _tickCounter.ElapsedTicks * _hostTickFreq; + + return (ulong)(ticks * CntfrqEl0); + } + } + + public long TpidrEl0 { get; set; } + public long Tpidr { get; set; } + + public FPCR Fpcr { get; set; } + public FPSR Fpsr { get; set; } + + public bool IsAarch32 { get; set; } + + internal ExecutionMode ExecutionMode + { + get + { + if (IsAarch32) + { + return GetPstateFlag(PState.TFlag) + ? ExecutionMode.Aarch32Thumb + : ExecutionMode.Aarch32Arm; + } + else + { + return ExecutionMode.Aarch64; + } + } + } + + public bool Running { get; set; } + + public event EventHandler<EventArgs> Interrupt; + public event EventHandler<InstExceptionEventArgs> Break; + public event EventHandler<InstExceptionEventArgs> SupervisorCall; + public event EventHandler<InstUndefinedEventArgs> Undefined; + + static ExecutionContext() + { + _hostTickFreq = 1.0 / Stopwatch.Frequency; + + _tickCounter = new Stopwatch(); + + _tickCounter.Start(); + } + + public ExecutionContext() + { + _nativeContext = new NativeContext(); + + Running = true; + + _nativeContext.SetCounter(MinCountForCheck); + } + + public ulong GetX(int index) => _nativeContext.GetX(index); + public void SetX(int index, ulong value) => _nativeContext.SetX(index, value); + + public V128 GetV(int index) => _nativeContext.GetV(index); + public void SetV(int index, V128 value) => _nativeContext.SetV(index, value); + + public bool GetPstateFlag(PState flag) => _nativeContext.GetPstateFlag(flag); + public void SetPstateFlag(PState flag, bool value) => _nativeContext.SetPstateFlag(flag, value); + + internal void CheckInterrupt() + { + if (_interrupted) + { + _interrupted = false; + + Interrupt?.Invoke(this, EventArgs.Empty); + } + + _nativeContext.SetCounter(MinCountForCheck); + } + + public void RequestInterrupt() + { + _interrupted = true; + } + + internal void OnBreak(ulong address, int imm) + { + Break?.Invoke(this, new InstExceptionEventArgs(address, imm)); + } + + internal void OnSupervisorCall(ulong address, int imm) + { + SupervisorCall?.Invoke(this, new InstExceptionEventArgs(address, imm)); + } + + internal void OnUndefined(ulong address, int opCode) + { + Undefined?.Invoke(this, new InstUndefinedEventArgs(address, opCode)); + } + + public void Dispose() + { + _nativeContext.Dispose(); + } + } +} \ No newline at end of file diff --git a/ARMeilleure/State/ExecutionMode.cs b/ARMeilleure/State/ExecutionMode.cs new file mode 100644 index 00000000..eaed9d27 --- /dev/null +++ b/ARMeilleure/State/ExecutionMode.cs @@ -0,0 +1,9 @@ +namespace ARMeilleure.State +{ + enum ExecutionMode + { + Aarch32Arm, + Aarch32Thumb, + Aarch64 + } +} \ No newline at end of file diff --git a/ARMeilleure/State/FPCR.cs b/ARMeilleure/State/FPCR.cs new file mode 100644 index 00000000..511681fa --- /dev/null +++ b/ARMeilleure/State/FPCR.cs @@ -0,0 +1,23 @@ +using System; + +namespace ARMeilleure.State +{ + [Flags] + public enum FPCR + { + Ufe = 1 << 11, + Fz = 1 << 24, + Dn = 1 << 25, + Ahp = 1 << 26 + } + + public static class FPCRExtensions + { + private const int RModeShift = 22; + + public static FPRoundingMode GetRoundingMode(this FPCR fpcr) + { + return (FPRoundingMode)(((int)fpcr >> RModeShift) & 3); + } + } +} diff --git a/ARMeilleure/State/FPException.cs b/ARMeilleure/State/FPException.cs new file mode 100644 index 00000000..e24e07af --- /dev/null +++ b/ARMeilleure/State/FPException.cs @@ -0,0 +1,12 @@ +namespace ARMeilleure.State +{ + enum FPException + { + InvalidOp = 0, + DivideByZero = 1, + Overflow = 2, + Underflow = 3, + Inexact = 4, + InputDenorm = 7 + } +} diff --git a/ARMeilleure/State/FPRoundingMode.cs b/ARMeilleure/State/FPRoundingMode.cs new file mode 100644 index 00000000..ee4f8766 --- /dev/null +++ b/ARMeilleure/State/FPRoundingMode.cs @@ -0,0 +1,10 @@ +namespace ARMeilleure.State +{ + public enum FPRoundingMode + { + ToNearest = 0, + TowardsPlusInfinity = 1, + TowardsMinusInfinity = 2, + TowardsZero = 3 + } +} diff --git a/ARMeilleure/State/FPSR.cs b/ARMeilleure/State/FPSR.cs new file mode 100644 index 00000000..c20dc439 --- /dev/null +++ b/ARMeilleure/State/FPSR.cs @@ -0,0 +1,11 @@ +using System; + +namespace ARMeilleure.State +{ + [Flags] + public enum FPSR + { + Ufc = 1 << 3, + Qc = 1 << 27 + } +} diff --git a/ARMeilleure/State/FPType.cs b/ARMeilleure/State/FPType.cs new file mode 100644 index 00000000..84e0db8d --- /dev/null +++ b/ARMeilleure/State/FPType.cs @@ -0,0 +1,11 @@ +namespace ARMeilleure.State +{ + enum FPType + { + Nonzero, + Zero, + Infinity, + QNaN, + SNaN + } +} diff --git a/ARMeilleure/State/IExecutionContext.cs b/ARMeilleure/State/IExecutionContext.cs new file mode 100644 index 00000000..df91b7a1 --- /dev/null +++ b/ARMeilleure/State/IExecutionContext.cs @@ -0,0 +1,37 @@ +using System; + +namespace ARMeilleure.State +{ + public interface IExecutionContext : IDisposable + { + uint CtrEl0 { get; } + uint DczidEl0 { get; } + + ulong CntfrqEl0 { get; set; } + ulong CntpctEl0 { get; } + + long TpidrEl0 { get; set; } + long Tpidr { get; set; } + + FPCR Fpcr { get; set; } + FPSR Fpsr { get; set; } + + bool IsAarch32 { get; set; } + + bool Running { get; set; } + + event EventHandler<EventArgs> Interrupt; + event EventHandler<InstExceptionEventArgs> Break; + event EventHandler<InstExceptionEventArgs> SupervisorCall; + event EventHandler<InstUndefinedEventArgs> Undefined; + + ulong GetX(int index); + void SetX(int index, ulong value); + + V128 GetV(int index); + + bool GetPstateFlag(PState flag); + + void RequestInterrupt(); + } +} \ No newline at end of file diff --git a/ARMeilleure/State/InstExceptionEventArgs.cs b/ARMeilleure/State/InstExceptionEventArgs.cs new file mode 100644 index 00000000..c2460e4b --- /dev/null +++ b/ARMeilleure/State/InstExceptionEventArgs.cs @@ -0,0 +1,16 @@ +using System; + +namespace ARMeilleure.State +{ + public class InstExceptionEventArgs : EventArgs + { + public ulong Address { get; } + public int Id { get; } + + public InstExceptionEventArgs(ulong address, int id) + { + Address = address; + Id = id; + } + } +} \ No newline at end of file diff --git a/ARMeilleure/State/InstUndefinedEventArgs.cs b/ARMeilleure/State/InstUndefinedEventArgs.cs new file mode 100644 index 00000000..c02b648e --- /dev/null +++ b/ARMeilleure/State/InstUndefinedEventArgs.cs @@ -0,0 +1,16 @@ +using System; + +namespace ARMeilleure.State +{ + public class InstUndefinedEventArgs : EventArgs + { + public ulong Address { get; } + public int OpCode { get; } + + public InstUndefinedEventArgs(ulong address, int opCode) + { + Address = address; + OpCode = opCode; + } + } +} \ No newline at end of file diff --git a/ARMeilleure/State/NativeContext.cs b/ARMeilleure/State/NativeContext.cs new file mode 100644 index 00000000..4e6a5302 --- /dev/null +++ b/ARMeilleure/State/NativeContext.cs @@ -0,0 +1,157 @@ +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Memory; +using System; +using System.Runtime.InteropServices; + +namespace ARMeilleure.State +{ + class NativeContext : IDisposable + { + private const int IntSize = 8; + private const int VecSize = 16; + private const int FlagSize = 4; + private const int ExtraSize = 4; + + private const int TotalSize = RegisterConsts.IntRegsCount * IntSize + + RegisterConsts.VecRegsCount * VecSize + + RegisterConsts.FlagsCount * FlagSize + ExtraSize; + + public IntPtr BasePtr { get; } + + public NativeContext() + { + BasePtr = MemoryManagement.Allocate(TotalSize); + } + + public ulong GetX(int index) + { + if ((uint)index >= RegisterConsts.IntRegsCount) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + return (ulong)Marshal.ReadInt64(BasePtr, index * IntSize); + } + + public void SetX(int index, ulong value) + { + if ((uint)index >= RegisterConsts.IntRegsCount) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + Marshal.WriteInt64(BasePtr, index * IntSize, (long)value); + } + + public V128 GetV(int index) + { + if ((uint)index >= RegisterConsts.IntRegsCount) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + int offset = RegisterConsts.IntRegsCount * IntSize + index * VecSize; + + return new V128( + Marshal.ReadInt64(BasePtr, offset + 0), + Marshal.ReadInt64(BasePtr, offset + 8)); + } + + public void SetV(int index, V128 value) + { + if ((uint)index >= RegisterConsts.IntRegsCount) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + int offset = RegisterConsts.IntRegsCount * IntSize + index * VecSize; + + Marshal.WriteInt64(BasePtr, offset + 0, value.GetInt64(0)); + Marshal.WriteInt64(BasePtr, offset + 8, value.GetInt64(1)); + } + + public bool GetPstateFlag(PState flag) + { + if ((uint)flag >= RegisterConsts.FlagsCount) + { + throw new ArgumentException($"Invalid flag \"{flag}\" specified."); + } + + int offset = + RegisterConsts.IntRegsCount * IntSize + + RegisterConsts.VecRegsCount * VecSize + (int)flag * FlagSize; + + int value = Marshal.ReadInt32(BasePtr, offset); + + return value != 0; + } + + public void SetPstateFlag(PState flag, bool value) + { + if ((uint)flag >= RegisterConsts.FlagsCount) + { + throw new ArgumentException($"Invalid flag \"{flag}\" specified."); + } + + int offset = + RegisterConsts.IntRegsCount * IntSize + + RegisterConsts.VecRegsCount * VecSize + (int)flag * FlagSize; + + Marshal.WriteInt32(BasePtr, offset, value ? 1 : 0); + } + + public int GetCounter() + { + return Marshal.ReadInt32(BasePtr, GetCounterOffset()); + } + + public void SetCounter(int value) + { + Marshal.WriteInt32(BasePtr, GetCounterOffset(), value); + } + + public static int GetRegisterOffset(Register reg) + { + int offset, size; + + if (reg.Type == RegisterType.Integer) + { + offset = reg.Index * IntSize; + + size = IntSize; + } + else if (reg.Type == RegisterType.Vector) + { + offset = RegisterConsts.IntRegsCount * IntSize + reg.Index * VecSize; + + size = VecSize; + } + else /* if (reg.Type == RegisterType.Flag) */ + { + offset = RegisterConsts.IntRegsCount * IntSize + + RegisterConsts.VecRegsCount * VecSize + reg.Index * FlagSize; + + size = FlagSize; + } + + if ((uint)(offset + size) > (uint)TotalSize) + { + throw new ArgumentException("Invalid register."); + } + + return offset; + } + + public static int GetCounterOffset() + { + return RegisterConsts.IntRegsCount * IntSize + + RegisterConsts.VecRegsCount * VecSize + + RegisterConsts.FlagsCount * FlagSize; + } + + public void Dispose() + { + MemoryManagement.Free(BasePtr); + } + } +} \ No newline at end of file diff --git a/ARMeilleure/State/PState.cs b/ARMeilleure/State/PState.cs new file mode 100644 index 00000000..ce755e95 --- /dev/null +++ b/ARMeilleure/State/PState.cs @@ -0,0 +1,16 @@ +using System; + +namespace ARMeilleure.State +{ + [Flags] + public enum PState + { + TFlag = 5, + EFlag = 9, + + VFlag = 28, + CFlag = 29, + ZFlag = 30, + NFlag = 31 + } +} diff --git a/ARMeilleure/State/RegisterAlias.cs b/ARMeilleure/State/RegisterAlias.cs new file mode 100644 index 00000000..ae0d4562 --- /dev/null +++ b/ARMeilleure/State/RegisterAlias.cs @@ -0,0 +1,41 @@ +namespace ARMeilleure.State +{ + static class RegisterAlias + { + public const int R8Usr = 8; + public const int R9Usr = 9; + public const int R10Usr = 10; + public const int R11Usr = 11; + public const int R12Usr = 12; + public const int SpUsr = 13; + public const int LrUsr = 14; + + public const int SpHyp = 15; + + public const int LrIrq = 16; + public const int SpIrq = 17; + + public const int LrSvc = 18; + public const int SpSvc = 19; + + public const int LrAbt = 20; + public const int SpAbt = 21; + + public const int LrUnd = 22; + public const int SpUnd = 23; + + public const int R8Fiq = 24; + public const int R9Fiq = 25; + public const int R10Fiq = 26; + public const int R11Fiq = 27; + public const int R12Fiq = 28; + public const int SpFiq = 29; + public const int LrFiq = 30; + + public const int Aarch32Lr = 14; + public const int Aarch32Pc = 15; + + public const int Lr = 30; + public const int Zr = 31; + } +} \ No newline at end of file diff --git a/ARMeilleure/State/RegisterConsts.cs b/ARMeilleure/State/RegisterConsts.cs new file mode 100644 index 00000000..a85117bb --- /dev/null +++ b/ARMeilleure/State/RegisterConsts.cs @@ -0,0 +1,13 @@ +namespace ARMeilleure.State +{ + static class RegisterConsts + { + public const int IntRegsCount = 32; + public const int VecRegsCount = 32; + public const int FlagsCount = 32; + public const int IntAndVecRegsCount = IntRegsCount + VecRegsCount; + public const int TotalCount = IntRegsCount + VecRegsCount + FlagsCount; + + public const int ZeroIndex = 31; + } +} \ No newline at end of file diff --git a/ARMeilleure/State/V128.cs b/ARMeilleure/State/V128.cs new file mode 100644 index 00000000..eeb9ff1c --- /dev/null +++ b/ARMeilleure/State/V128.cs @@ -0,0 +1,214 @@ +using System; + +namespace ARMeilleure.State +{ + public struct V128 : IEquatable<V128> + { + private ulong _e0; + private ulong _e1; + + private static V128 _zero = new V128(0, 0); + + public static V128 Zero => _zero; + + public V128(float value) : this(value, 0, 0, 0) { } + + public V128(double value) : this(value, 0) { } + + public V128(float e0, float e1, float e2, float e3) + { + _e0 = (ulong)(uint)BitConverter.SingleToInt32Bits(e0) << 0; + _e0 |= (ulong)(uint)BitConverter.SingleToInt32Bits(e1) << 32; + _e1 = (ulong)(uint)BitConverter.SingleToInt32Bits(e2) << 0; + _e1 |= (ulong)(uint)BitConverter.SingleToInt32Bits(e3) << 32; + } + + public V128(double e0, double e1) + { + _e0 = (ulong)BitConverter.DoubleToInt64Bits(e0); + _e1 = (ulong)BitConverter.DoubleToInt64Bits(e1); + } + + public V128(int e0, int e1, int e2, int e3) + { + _e0 = (ulong)(uint)e0 << 0; + _e0 |= (ulong)(uint)e1 << 32; + _e1 = (ulong)(uint)e2 << 0; + _e1 |= (ulong)(uint)e3 << 32; + } + + public V128(uint e0, uint e1, uint e2, uint e3) + { + _e0 = (ulong)e0 << 0; + _e0 |= (ulong)e1 << 32; + _e1 = (ulong)e2 << 0; + _e1 |= (ulong)e3 << 32; + } + + public V128(long e0, long e1) + { + _e0 = (ulong)e0; + _e1 = (ulong)e1; + } + + public V128(ulong e0, ulong e1) + { + _e0 = e0; + _e1 = e1; + } + + public V128(byte[] data) + { + _e0 = (ulong)BitConverter.ToInt64(data, 0); + _e1 = (ulong)BitConverter.ToInt64(data, 8); + } + + public void Insert(int index, uint value) + { + switch (index) + { + case 0: _e0 = (_e0 & 0xffffffff00000000) | ((ulong)value << 0); break; + case 1: _e0 = (_e0 & 0x00000000ffffffff) | ((ulong)value << 32); break; + case 2: _e1 = (_e1 & 0xffffffff00000000) | ((ulong)value << 0); break; + case 3: _e1 = (_e1 & 0x00000000ffffffff) | ((ulong)value << 32); break; + + default: throw new ArgumentOutOfRangeException(nameof(index)); + } + } + + public void Insert(int index, ulong value) + { + switch (index) + { + case 0: _e0 = value; break; + case 1: _e1 = value; break; + + default: throw new ArgumentOutOfRangeException(nameof(index)); + } + } + + public float AsFloat() + { + return GetFloat(0); + } + + public double AsDouble() + { + return GetDouble(0); + } + + public float GetFloat(int index) + { + return BitConverter.Int32BitsToSingle(GetInt32(index)); + } + + public double GetDouble(int index) + { + return BitConverter.Int64BitsToDouble(GetInt64(index)); + } + + public int GetInt32(int index) => (int)GetUInt32(index); + public long GetInt64(int index) => (long)GetUInt64(index); + + public uint GetUInt32(int index) + { + switch (index) + { + case 0: return (uint)(_e0 >> 0); + case 1: return (uint)(_e0 >> 32); + case 2: return (uint)(_e1 >> 0); + case 3: return (uint)(_e1 >> 32); + } + + throw new ArgumentOutOfRangeException(nameof(index)); + } + + public ulong GetUInt64(int index) + { + switch (index) + { + case 0: return _e0; + case 1: return _e1; + } + + throw new ArgumentOutOfRangeException(nameof(index)); + } + + public byte[] ToArray() + { + byte[] e0Data = BitConverter.GetBytes(_e0); + byte[] e1Data = BitConverter.GetBytes(_e1); + + byte[] data = new byte[16]; + + Buffer.BlockCopy(e0Data, 0, data, 0, 8); + Buffer.BlockCopy(e1Data, 0, data, 8, 8); + + return data; + } + + public override int GetHashCode() + { + return HashCode.Combine(_e0, _e1); + } + + public static V128 operator ~(V128 x) + { + return new V128(~x._e0, ~x._e1); + } + + public static V128 operator &(V128 x, V128 y) + { + return new V128(x._e0 & y._e0, x._e1 & y._e1); + } + + public static V128 operator |(V128 x, V128 y) + { + return new V128(x._e0 | y._e0, x._e1 | y._e1); + } + + public static V128 operator ^(V128 x, V128 y) + { + return new V128(x._e0 ^ y._e0, x._e1 ^ y._e1); + } + + public static V128 operator <<(V128 x, int shift) + { + ulong shiftOut = x._e0 >> (64 - shift); + + return new V128(x._e0 << shift, (x._e1 << shift) | shiftOut); + } + + public static V128 operator >>(V128 x, int shift) + { + ulong shiftOut = x._e1 & ((1UL << shift) - 1); + + return new V128((x._e0 >> shift) | (shiftOut << (64 - shift)), x._e1 >> shift); + } + + public static bool operator ==(V128 x, V128 y) + { + return x.Equals(y); + } + + public static bool operator !=(V128 x, V128 y) + { + return !x.Equals(y); + } + + public override bool Equals(object obj) + { + return obj is V128 vector && Equals(vector); + } + + public bool Equals(V128 other) + { + return other._e0 == _e0 && other._e1 == _e1; + } + + public override string ToString() + { + return $"0x{_e1:X16}{_e0:X16}"; + } + } +} \ No newline at end of file -- cgit v1.2.3-70-g09d2