From b233ae964fcaae900cdefa6ce51b0edb2892dfaf Mon Sep 17 00:00:00 2001
From: Merry <MerryMage@users.noreply.github.com>
Date: Thu, 12 Jul 2018 19:51:02 +0100
Subject: [PATCH] AInstEmitSimdCvt: Half-precision to single-precision
 conversion (#235)

---
 ChocolArm64/Instruction/AInstEmitSimdCvt.cs |  8 ++---
 ChocolArm64/Instruction/ASoftFloat.cs       | 36 +++++++++++++++++++
 Ryujinx.Tests/Cpu/CpuTestSimdCvt.cs         | 40 +++++++++++++++++++++
 3 files changed, 80 insertions(+), 4 deletions(-)
 create mode 100644 Ryujinx.Tests/Cpu/CpuTestSimdCvt.cs

diff --git a/ChocolArm64/Instruction/AInstEmitSimdCvt.cs b/ChocolArm64/Instruction/AInstEmitSimdCvt.cs
index 98bb972a2d..da584743c3 100644
--- a/ChocolArm64/Instruction/AInstEmitSimdCvt.cs
+++ b/ChocolArm64/Instruction/AInstEmitSimdCvt.cs
@@ -45,10 +45,10 @@ namespace ChocolArm64.Instruction
             {
                 if (SizeF == 0)
                 {
-                    //TODO: This need the half precision floating point type,
-                    //that is not yet supported on .NET. We should probably
-                    //do our own implementation on the meantime.
-                    throw new NotImplementedException();
+                    EmitVectorExtractZx(Context, Op.Rn, Part + Index, 1);
+                    Context.Emit(OpCodes.Conv_U2);
+
+                    Context.EmitCall(typeof(ASoftFloat), nameof(ASoftFloat.ConvertHalfToSingle));
                 }
                 else /* if (SizeF == 1) */
                 {
diff --git a/ChocolArm64/Instruction/ASoftFloat.cs b/ChocolArm64/Instruction/ASoftFloat.cs
index e63c82beea..27f4f7fb4f 100644
--- a/ChocolArm64/Instruction/ASoftFloat.cs
+++ b/ChocolArm64/Instruction/ASoftFloat.cs
@@ -225,5 +225,41 @@ namespace ChocolArm64.Instruction
 
             return 2.0 + op1 * op2;
         }
+
+        public static float ConvertHalfToSingle(ushort x)
+        {
+            uint x_sign = (uint)(x >> 15) & 0x0001;
+            uint x_exp = (uint)(x >> 10) & 0x001F;
+            uint x_mantissa = (uint)x & 0x03FF;
+
+            if (x_exp == 0 && x_mantissa == 0)
+            {
+                // Zero
+                return BitConverter.Int32BitsToSingle((int)(x_sign << 31));
+            }
+
+            if (x_exp == 0x1F)
+            {
+                // NaN or Infinity
+                return BitConverter.Int32BitsToSingle((int)((x_sign << 31) | 0x7F800000 | (x_mantissa << 13)));
+            }
+
+            int exponent = (int)x_exp - 15;
+
+            if (x_exp == 0)
+            {
+                // Denormal
+                x_mantissa <<= 1;
+                while ((x_mantissa & 0x0400) == 0)
+                {
+                    x_mantissa <<= 1;
+                    exponent--;
+                }
+                x_mantissa &= 0x03FF;
+            }
+
+            uint new_exp = (uint)((exponent + 127) & 0xFF) << 23;
+            return BitConverter.Int32BitsToSingle((int)((x_sign << 31) | new_exp | (x_mantissa << 13)));
+        }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.Tests/Cpu/CpuTestSimdCvt.cs b/Ryujinx.Tests/Cpu/CpuTestSimdCvt.cs
new file mode 100644
index 0000000000..2d021616c6
--- /dev/null
+++ b/Ryujinx.Tests/Cpu/CpuTestSimdCvt.cs
@@ -0,0 +1,40 @@
+using ChocolArm64.State;
+
+using NUnit.Framework;
+
+using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.X86;
+
+namespace Ryujinx.Tests.Cpu
+{
+    public class CpuTestSimdCvt : CpuTest
+    {
+        [TestCase((ushort)0x0000, 0x00000000u)] // Positive Zero
+        [TestCase((ushort)0x8000, 0x80000000u)] // Negative Zero
+        [TestCase((ushort)0x3E00, 0x3FC00000u)] // +1.5
+        [TestCase((ushort)0xBE00, 0xBFC00000u)] // -1.5
+        [TestCase((ushort)0xFFFF, 0xFFFFE000u)] // -QNaN
+        [TestCase((ushort)0x7C00, 0x7F800000u)] // +Inf
+        [TestCase((ushort)0x3C00, 0x3F800000u)] // 1.0
+        [TestCase((ushort)0x3C01, 0x3F802000u)] // 1.0009765625
+        [TestCase((ushort)0xC000, 0xC0000000u)] // -2.0
+        [TestCase((ushort)0x7BFF, 0x477FE000u)] // 65504.0 (Largest Normal)
+        [TestCase((ushort)0x03FF, 0x387FC000u)] // 0.00006097555 (Largest Subnormal)
+        [TestCase((ushort)0x0001, 0x33800000u)] // 5.96046448e-8 (Smallest Subnormal)
+        public void Fcvtl_V_f16(ushort Value, uint Result)
+        {
+            uint Opcode = 0x0E217801;
+            Vector128<float> V0 = Sse.StaticCast<ushort, float>(Sse2.SetAllVector128(Value));
+
+            AThreadState ThreadState = SingleOpcode(Opcode, V0: V0);
+
+            Assert.Multiple(() =>
+            {
+                Assert.That(Sse41.Extract(Sse.StaticCast<float, uint>(ThreadState.V1), (byte)0), Is.EqualTo(Result));
+                Assert.That(Sse41.Extract(Sse.StaticCast<float, uint>(ThreadState.V1), (byte)1), Is.EqualTo(Result));
+                Assert.That(Sse41.Extract(Sse.StaticCast<float, uint>(ThreadState.V1), (byte)2), Is.EqualTo(Result));
+                Assert.That(Sse41.Extract(Sse.StaticCast<float, uint>(ThreadState.V1), (byte)3), Is.EqualTo(Result));
+            });
+        }
+    }
+}