From ffbfbb554965651194a58ca92a3a5db195cdc7ed Mon Sep 17 00:00:00 2001
From: LDj3SNuD <35856442+LDj3SNuD@users.noreply.github.com>
Date: Fri, 31 May 2019 00:51:39 +0200
Subject: [PATCH] Add FCVT <Hd>, <Sn> and FCVT <Sd>, <Hn> Inst.; add Tests.
 (#692)

* Update OpCodeTable.cs

* Update InstEmitSimdCvt.cs

* Update CpuTestSimd.cs

* Address PR feedback.
---
 ChocolArm64/Instructions/InstEmitSimdCvt.cs | 80 ++++++++++++++-----
 ChocolArm64/OpCodeTable.cs                  |  2 +-
 Ryujinx.Tests/Cpu/CpuTestSimd.cs            | 88 ++++++++++++++++++++-
 3 files changed, 146 insertions(+), 24 deletions(-)

diff --git a/ChocolArm64/Instructions/InstEmitSimdCvt.cs b/ChocolArm64/Instructions/InstEmitSimdCvt.cs
index b34687e1c3..2b3deb98e0 100644
--- a/ChocolArm64/Instructions/InstEmitSimdCvt.cs
+++ b/ChocolArm64/Instructions/InstEmitSimdCvt.cs
@@ -16,23 +16,10 @@ namespace ChocolArm64.Instructions
         {
             OpCodeSimd64 op = (OpCodeSimd64)context.CurrOp;
 
-            if (Optimizations.UseSse2)
+            if (op.Size == 0 && op.Opc == 1) // Single -> Double.
             {
-                if (op.Size == 1 && op.Opc == 0)
+                if (Optimizations.UseSse2)
                 {
-                    //Double -> Single.
-                    Type[] typesCvt = new Type[] { typeof(Vector128<float>), typeof(Vector128<double>) };
-
-                    VectorHelper.EmitCall(context, nameof(VectorHelper.VectorSingleZero));
-                    context.EmitLdvec(op.Rn);
-
-                    context.EmitCall(typeof(Sse2).GetMethod(nameof(Sse2.ConvertScalarToVector128Single), typesCvt));
-
-                    context.EmitStvec(op.Rd);
-                }
-                else if (op.Size == 0 && op.Opc == 1)
-                {
-                    //Single -> Double.
                     Type[] typesCvt = new Type[] { typeof(Vector128<double>), typeof(Vector128<float>) };
 
                     VectorHelper.EmitCall(context, nameof(VectorHelper.VectorSingleZero));
@@ -44,17 +31,68 @@ namespace ChocolArm64.Instructions
                 }
                 else
                 {
-                    //Invalid encoding.
-                    throw new InvalidOperationException();
+                    EmitVectorExtractF(context, op.Rn, 0, 0);
+
+                    EmitFloatCast(context, 1);
+
+                    EmitScalarSetF(context, op.Rd, 1);
                 }
             }
-            else
+            else if (op.Size == 1 && op.Opc == 0) // Double -> Single.
             {
-                EmitVectorExtractF(context, op.Rn, 0, op.Size);
+                if (Optimizations.UseSse2)
+                {
+                    Type[] typesCvt = new Type[] { typeof(Vector128<float>), typeof(Vector128<double>) };
 
-                EmitFloatCast(context, op.Opc);
+                    VectorHelper.EmitCall(context, nameof(VectorHelper.VectorSingleZero));
+                    context.EmitLdvec(op.Rn);
 
-                EmitScalarSetF(context, op.Rd, op.Opc);
+                    context.EmitCall(typeof(Sse2).GetMethod(nameof(Sse2.ConvertScalarToVector128Single), typesCvt));
+
+                    context.EmitStvec(op.Rd);
+                }
+                else
+                {
+                    EmitVectorExtractF(context, op.Rn, 0, 1);
+
+                    EmitFloatCast(context, 0);
+
+                    EmitScalarSetF(context, op.Rd, 0);
+                }
+            }
+            else if (op.Size == 0 && op.Opc == 3) // Single -> Half.
+            {
+                EmitVectorExtractF(context, op.Rn, 0, 0);
+
+                context.EmitLdarg(TranslatedSub.StateArgIdx);
+
+                context.EmitCall(typeof(SoftFloat32_16), nameof(SoftFloat32_16.FPConvert));
+
+                context.Emit(OpCodes.Conv_U8);
+                EmitScalarSet(context, op.Rd, 1);
+            }
+            else if (op.Size == 3 && op.Opc == 0) // Half -> Single.
+            {
+                EmitVectorExtractZx(context, op.Rn, 0, 1);
+                context.Emit(OpCodes.Conv_U2);
+
+                context.EmitLdarg(TranslatedSub.StateArgIdx);
+
+                context.EmitCall(typeof(SoftFloat16_32), nameof(SoftFloat16_32.FPConvert));
+
+                EmitScalarSetF(context, op.Rd, 0);
+            }
+            else if (op.Size == 1 && op.Opc == 3) // Double -> Half.
+            {
+                throw new NotImplementedException("Double-precision to half-precision.");
+            }
+            else if (op.Size == 3 && op.Opc == 1) // Double -> Half.
+            {
+                throw new NotImplementedException("Half-precision to double-precision.");
+            }
+            else // Invalid encoding.
+            {
+                throw new InvalidOperationException($"type == {op.Size} && opc == {op.Opc}");
             }
         }
 
diff --git a/ChocolArm64/OpCodeTable.cs b/ChocolArm64/OpCodeTable.cs
index a1bbd4bc0f..2200faefd1 100644
--- a/ChocolArm64/OpCodeTable.cs
+++ b/ChocolArm64/OpCodeTable.cs
@@ -296,7 +296,7 @@ namespace ChocolArm64
             SetA64("000111100x1xxxxx001000xxxxx0x000", InstEmit.Fcmp_S,          typeof(OpCodeSimdReg64));
             SetA64("000111100x1xxxxx001000xxxxx1x000", InstEmit.Fcmpe_S,         typeof(OpCodeSimdReg64));
             SetA64("000111100x1xxxxxxxxx11xxxxxxxxxx", InstEmit.Fcsel_S,         typeof(OpCodeSimdFcond64));
-            SetA64("000111100x10001xx10000xxxxxxxxxx", InstEmit.Fcvt_S,          typeof(OpCodeSimd64));
+            SetA64("00011110xx10001xx10000xxxxxxxxxx", InstEmit.Fcvt_S,          typeof(OpCodeSimd64));
             SetA64("x00111100x100100000000xxxxxxxxxx", InstEmit.Fcvtas_Gp,       typeof(OpCodeSimdCvt64));
             SetA64("x00111100x100101000000xxxxxxxxxx", InstEmit.Fcvtau_Gp,       typeof(OpCodeSimdCvt64));
             SetA64("0x0011100x100001011110xxxxxxxxxx", InstEmit.Fcvtl_V,         typeof(OpCodeSimd64));
diff --git a/Ryujinx.Tests/Cpu/CpuTestSimd.cs b/Ryujinx.Tests/Cpu/CpuTestSimd.cs
index d6bb7318a6..6125344c56 100644
--- a/Ryujinx.Tests/Cpu/CpuTestSimd.cs
+++ b/Ryujinx.Tests/Cpu/CpuTestSimd.cs
@@ -90,6 +90,48 @@ namespace Ryujinx.Tests.Cpu
                                  0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul };
         }
 
+        private static IEnumerable<ulong> _1H_F_()
+        {
+            yield return 0x000000000000FBFFul; // -Max Normal
+            yield return 0x0000000000008400ul; // -Min Normal
+            yield return 0x00000000000083FFul; // -Max Subnormal
+            yield return 0x0000000000008001ul; // -Min Subnormal
+            yield return 0x0000000000007BFFul; // +Max Normal
+            yield return 0x0000000000000400ul; // +Min Normal
+            yield return 0x00000000000003FFul; // +Max Subnormal
+            yield return 0x0000000000000001ul; // +Min Subnormal
+
+            if (!NoZeros)
+            {
+                yield return 0x0000000000008000ul; // -Zero
+                yield return 0x0000000000000000ul; // +Zero
+            }
+
+            if (!NoInfs)
+            {
+                yield return 0x000000000000FC00ul; // -Infinity
+                yield return 0x0000000000007C00ul; // +Infinity
+            }
+
+            if (!NoNaNs)
+            {
+                yield return 0x000000000000FE00ul; // -QNaN (all zeros payload)
+                yield return 0x000000000000FDFFul; // -SNaN (all ones  payload)
+                yield return 0x0000000000007E00ul; // +QNaN (all zeros payload) (DefaultNaN)
+                yield return 0x0000000000007DFFul; // +SNaN (all ones  payload)
+            }
+
+            for (int cnt = 1; cnt <= RndCnt; cnt++)
+            {
+                ulong grbg = TestContext.CurrentContext.Random.NextUShort();
+                ulong rnd1 = GenNormalH();
+                ulong rnd2 = GenSubnormalH();
+
+                yield return (grbg << 48) | (grbg << 32) | (grbg << 16) | rnd1;
+                yield return (grbg << 48) | (grbg << 32) | (grbg << 16) | rnd2;
+            }
+        }
+
         private static IEnumerable<ulong> _4H_F_()
         {
             yield return 0xFBFFFBFFFBFFFBFFul; // -Max Normal
@@ -123,8 +165,8 @@ namespace Ryujinx.Tests.Cpu
 
             for (int cnt = 1; cnt <= RndCnt; cnt++)
             {
-                uint rnd1 = (uint)GenNormalH();
-                uint rnd2 = (uint)GenSubnormalH();
+                ulong rnd1 = GenNormalH();
+                ulong rnd2 = GenSubnormalH();
 
                 yield return (rnd1 << 48) | (rnd1 << 32) | (rnd1 << 16) | rnd1;
                 yield return (rnd2 << 48) | (rnd2 << 32) | (rnd2 << 16) | rnd2;
@@ -609,6 +651,22 @@ namespace Ryujinx.Tests.Cpu
             };
         }
 
+        private static uint[] _F_Cvt_S_SH_()
+        {
+            return new uint[]
+            {
+                0x1E23C020u // FCVT H0, S1
+            };
+        }
+
+        private static uint[] _F_Cvt_S_HS_()
+        {
+            return new uint[]
+            {
+                0x1EE24020u // FCVT S0, H1
+            };
+        }
+
         private static uint[] _F_Cvt_NZ_SU_S_S_()
         {
             return new uint[]
@@ -1614,6 +1672,32 @@ namespace Ryujinx.Tests.Cpu
             CompareAgainstUnicorn();
         }
 
+        [Test, Pairwise] [Explicit]
+        public void F_Cvt_S_SH([ValueSource("_F_Cvt_S_SH_")] uint opcodes,
+                               [ValueSource("_1S_F_")] ulong a)
+        {
+            ulong z = TestContext.CurrentContext.Random.NextULong();
+            Vector128<float> v0 = MakeVectorE0E1(z, z);
+            Vector128<float> v1 = MakeVectorE0(a);
+
+            SingleOpcode(opcodes, v0: v0, v1: v1);
+
+            CompareAgainstUnicorn();
+        }
+
+        [Test, Pairwise] [Explicit]
+        public void F_Cvt_S_HS([ValueSource("_F_Cvt_S_HS_")] uint opcodes,
+                               [ValueSource("_1H_F_")] ulong a)
+        {
+            ulong z = TestContext.CurrentContext.Random.NextULong();
+            Vector128<float> v0 = MakeVectorE0E1(z, z);
+            Vector128<float> v1 = MakeVectorE0(a);
+
+            SingleOpcode(opcodes, v0: v0, v1: v1);
+
+            CompareAgainstUnicorn();
+        }
+
         [Test, Pairwise] [Explicit]
         public void F_Cvt_NZ_SU_S_S([ValueSource("_F_Cvt_NZ_SU_S_S_")] uint opcodes,
                                     [ValueSource("_1S_F_W_")] ulong a)