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