Add Fmax/minv_V & S/Ushl_S Inst.s with Tests. Fix Maxps/d & Minps/d d… (#1335)

* Add Fmax/minv_V & S/Ushl_S Inst.s with Tests. Fix Maxps/d & Minps/d double zero sign handling. Allows better handling of NaNs.

* Optimized EmitSse2VectorIsNaNOpF() for multiple uses per opF.
This commit is contained in:
LDj3SNuD 2020-07-13 13:08:47 +02:00 committed by GitHub
parent d7044b10a2
commit a804db6eed
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 698 additions and 164 deletions

View file

@ -12,10 +12,14 @@ namespace Ryujinx.Tests.Cpu
[TestFixture]
public class CpuTest
{
private ulong _currAddress;
private ulong _size;
protected const ulong Size = 0x1000;
protected const ulong CodeBaseAddress = 0x1000;
protected const ulong DataBaseAddress = CodeBaseAddress + Size;
private ulong _entryPoint;
private const bool Ignore_FpcrFz_FpcrDn = false;
private const bool IgnoreAllExcept_FpsrQc = false;
private ulong _currAddress;
private MemoryBlock _ram;
@ -28,6 +32,8 @@ namespace Ryujinx.Tests.Cpu
private static bool _unicornAvailable;
private UnicornAArch64 _unicornEmu;
private bool _usingMemory;
static CpuTest()
{
_unicornAvailable = UnicornAArch64.IsAvailable();
@ -41,14 +47,11 @@ namespace Ryujinx.Tests.Cpu
[SetUp]
public void Setup()
{
_currAddress = 0x1000;
_size = 0x1000;
_currAddress = CodeBaseAddress;
_entryPoint = _currAddress;
_ram = new MemoryBlock(_size);
_memory = new MemoryManager(_ram, 1UL << 16);
_memory.Map(_currAddress, 0, _size);
_ram = new MemoryBlock(Size * 2);
_memory = new MemoryManager(_ram, 1ul << 16);
_memory.Map(CodeBaseAddress, 0, Size * 2);
_context = CpuContext.CreateExecutionContext();
@ -57,8 +60,9 @@ namespace Ryujinx.Tests.Cpu
if (_unicornAvailable)
{
_unicornEmu = new UnicornAArch64();
_unicornEmu.MemoryMap(_currAddress, _size, MemoryPermission.READ | MemoryPermission.EXEC);
_unicornEmu.PC = _entryPoint;
_unicornEmu.MemoryMap(CodeBaseAddress, Size, MemoryPermission.READ | MemoryPermission.EXEC);
_unicornEmu.MemoryMap(DataBaseAddress, Size, MemoryPermission.READ | MemoryPermission.WRITE);
_unicornEmu.PC = CodeBaseAddress;
}
}
@ -73,6 +77,8 @@ namespace Ryujinx.Tests.Cpu
_context = null;
_cpuContext = null;
_unicornEmu = null;
_usingMemory = false;
}
protected void Reset()
@ -169,11 +175,11 @@ namespace Ryujinx.Tests.Cpu
protected void ExecuteOpcodes(bool runUnicorn = true)
{
_cpuContext.Execute(_context, _entryPoint);
_cpuContext.Execute(_context, CodeBaseAddress);
if (_unicornAvailable && runUnicorn)
{
_unicornEmu.RunForCount((_currAddress - _entryPoint - 4) / 4);
_unicornEmu.RunForCount((_currAddress - CodeBaseAddress - 4) / 4);
}
}
@ -199,6 +205,11 @@ namespace Ryujinx.Tests.Cpu
int fpsr = 0,
bool runUnicorn = true)
{
if (Ignore_FpcrFz_FpcrDn)
{
fpcr &= ~((int)FPCR.Fz | (int)FPCR.Dn);
}
Opcode(opcode);
Opcode(0xD65F03C0); // RET
SetContext(x0, x1, x2, x3, x31, v0, v1, v2, v3, v4, v5, v30, v31, overflow, carry, zero, negative, fpcr, fpsr);
@ -207,6 +218,30 @@ namespace Ryujinx.Tests.Cpu
return GetContext();
}
protected void SetWorkingMemory(ulong offset, byte[] data)
{
_memory.Write(DataBaseAddress + offset, data);
if (_unicornAvailable)
{
_unicornEmu.MemoryWrite(DataBaseAddress + offset, data);
}
_usingMemory = true; // When true, CompareAgainstUnicorn checks the working memory for equality too.
}
protected void SetWorkingMemory(ulong offset, byte data)
{
_memory.Write(DataBaseAddress + offset, data);
if (_unicornAvailable)
{
_unicornEmu.MemoryWrite8(DataBaseAddress + offset, data);
}
_usingMemory = true; // When true, CompareAgainstUnicorn checks the working memory for equality too.
}
/// <summary>Rounding Mode control field.</summary>
public enum RMode
{
@ -284,15 +319,20 @@ namespace Ryujinx.Tests.Cpu
return;
}
if (IgnoreAllExcept_FpsrQc)
{
fpsrMask &= Fpsr.Qc;
}
if (fpSkips != FpSkips.None)
{
ManageFpSkips(fpSkips);
}
Assert.That(_context.GetX(0), Is.EqualTo(_unicornEmu.X[0]));
Assert.That(_context.GetX(1), Is.EqualTo(_unicornEmu.X[1]));
Assert.That(_context.GetX(2), Is.EqualTo(_unicornEmu.X[2]));
Assert.That(_context.GetX(3), Is.EqualTo(_unicornEmu.X[3]));
Assert.That(_context.GetX(0), Is.EqualTo(_unicornEmu.X[0]), "X0");
Assert.That(_context.GetX(1), Is.EqualTo(_unicornEmu.X[1]), "X1");
Assert.That(_context.GetX(2), Is.EqualTo(_unicornEmu.X[2]), "X2");
Assert.That(_context.GetX(3), Is.EqualTo(_unicornEmu.X[3]), "X3");
Assert.That(_context.GetX(4), Is.EqualTo(_unicornEmu.X[4]));
Assert.That(_context.GetX(5), Is.EqualTo(_unicornEmu.X[5]));
Assert.That(_context.GetX(6), Is.EqualTo(_unicornEmu.X[6]));
@ -321,21 +361,21 @@ namespace Ryujinx.Tests.Cpu
Assert.That(_context.GetX(29), Is.EqualTo(_unicornEmu.X[29]));
Assert.That(_context.GetX(30), Is.EqualTo(_unicornEmu.X[30]));
Assert.That(_context.GetX(31), Is.EqualTo(_unicornEmu.SP));
Assert.That(_context.GetX(31), Is.EqualTo(_unicornEmu.SP), "X31");
if (fpTolerances == FpTolerances.None)
{
Assert.That(V128ToSimdValue(_context.GetV(0)), Is.EqualTo(_unicornEmu.Q[0]));
Assert.That(V128ToSimdValue(_context.GetV(0)), Is.EqualTo(_unicornEmu.Q[0]), "V0");
}
else
{
ManageFpTolerances(fpTolerances);
}
Assert.That(V128ToSimdValue(_context.GetV(1)), Is.EqualTo(_unicornEmu.Q[1]));
Assert.That(V128ToSimdValue(_context.GetV(2)), Is.EqualTo(_unicornEmu.Q[2]));
Assert.That(V128ToSimdValue(_context.GetV(3)), Is.EqualTo(_unicornEmu.Q[3]));
Assert.That(V128ToSimdValue(_context.GetV(4)), Is.EqualTo(_unicornEmu.Q[4]));
Assert.That(V128ToSimdValue(_context.GetV(5)), Is.EqualTo(_unicornEmu.Q[5]));
Assert.That(V128ToSimdValue(_context.GetV(1)), Is.EqualTo(_unicornEmu.Q[1]), "V1");
Assert.That(V128ToSimdValue(_context.GetV(2)), Is.EqualTo(_unicornEmu.Q[2]), "V2");
Assert.That(V128ToSimdValue(_context.GetV(3)), Is.EqualTo(_unicornEmu.Q[3]), "V3");
Assert.That(V128ToSimdValue(_context.GetV(4)), Is.EqualTo(_unicornEmu.Q[4]), "V4");
Assert.That(V128ToSimdValue(_context.GetV(5)), Is.EqualTo(_unicornEmu.Q[5]), "V5");
Assert.That(V128ToSimdValue(_context.GetV(6)), Is.EqualTo(_unicornEmu.Q[6]));
Assert.That(V128ToSimdValue(_context.GetV(7)), Is.EqualTo(_unicornEmu.Q[7]));
Assert.That(V128ToSimdValue(_context.GetV(8)), Is.EqualTo(_unicornEmu.Q[8]));
@ -360,16 +400,27 @@ namespace Ryujinx.Tests.Cpu
Assert.That(V128ToSimdValue(_context.GetV(27)), Is.EqualTo(_unicornEmu.Q[27]));
Assert.That(V128ToSimdValue(_context.GetV(28)), Is.EqualTo(_unicornEmu.Q[28]));
Assert.That(V128ToSimdValue(_context.GetV(29)), Is.EqualTo(_unicornEmu.Q[29]));
Assert.That(V128ToSimdValue(_context.GetV(30)), Is.EqualTo(_unicornEmu.Q[30]));
Assert.That(V128ToSimdValue(_context.GetV(31)), Is.EqualTo(_unicornEmu.Q[31]));
Assert.That(V128ToSimdValue(_context.GetV(30)), Is.EqualTo(_unicornEmu.Q[30]), "V30");
Assert.That(V128ToSimdValue(_context.GetV(31)), Is.EqualTo(_unicornEmu.Q[31]), "V31");
Assert.That((int)_context.Fpcr, Is.EqualTo(_unicornEmu.Fpcr));
Assert.That((int)_context.Fpsr & (int)fpsrMask, Is.EqualTo(_unicornEmu.Fpsr & (int)fpsrMask));
Assert.That((int)_context.Fpcr, Is.EqualTo(_unicornEmu.Fpcr), "Fpcr");
Assert.That((int)_context.Fpsr & (int)fpsrMask, Is.EqualTo(_unicornEmu.Fpsr & (int)fpsrMask), "Fpsr");
Assert.That(_context.GetPstateFlag(PState.VFlag), Is.EqualTo(_unicornEmu.OverflowFlag));
Assert.That(_context.GetPstateFlag(PState.CFlag), Is.EqualTo(_unicornEmu.CarryFlag));
Assert.That(_context.GetPstateFlag(PState.ZFlag), Is.EqualTo(_unicornEmu.ZeroFlag));
Assert.That(_context.GetPstateFlag(PState.NFlag), Is.EqualTo(_unicornEmu.NegativeFlag));
Assert.Multiple(() =>
{
Assert.That(_context.GetPstateFlag(PState.VFlag), Is.EqualTo(_unicornEmu.OverflowFlag), "VFlag");
Assert.That(_context.GetPstateFlag(PState.CFlag), Is.EqualTo(_unicornEmu.CarryFlag), "CFlag");
Assert.That(_context.GetPstateFlag(PState.ZFlag), Is.EqualTo(_unicornEmu.ZeroFlag), "ZFlag");
Assert.That(_context.GetPstateFlag(PState.NFlag), Is.EqualTo(_unicornEmu.NegativeFlag), "NFlag");
});
if (_usingMemory)
{
byte[] mem = _memory.GetSpan(DataBaseAddress, (int)Size).ToArray();
byte[] unicornMem = _unicornEmu.MemoryRead(DataBaseAddress, Size);
Assert.That(mem, Is.EqualTo(unicornMem), "Data");
}
}
private void ManageFpSkips(FpSkips fpSkips)
@ -418,14 +469,17 @@ namespace Ryujinx.Tests.Cpu
if (IsNormalOrSubnormalS(_unicornEmu.Q[0].AsFloat()) &&
IsNormalOrSubnormalS(_context.GetV(0).As<float>()))
{
Assert.That (_context.GetV(0).Extract<float>(0),
Is.EqualTo(_unicornEmu.Q[0].GetFloat(0)).Within(1).Ulps);
Assert.That (_context.GetV(0).Extract<float>(1),
Is.EqualTo(_unicornEmu.Q[0].GetFloat(1)).Within(1).Ulps);
Assert.That (_context.GetV(0).Extract<float>(2),
Is.EqualTo(_unicornEmu.Q[0].GetFloat(2)).Within(1).Ulps);
Assert.That (_context.GetV(0).Extract<float>(3),
Is.EqualTo(_unicornEmu.Q[0].GetFloat(3)).Within(1).Ulps);
Assert.Multiple(() =>
{
Assert.That (_context.GetV(0).Extract<float>(0),
Is.EqualTo(_unicornEmu.Q[0].GetFloat(0)).Within(1).Ulps, "V0[0]");
Assert.That (_context.GetV(0).Extract<float>(1),
Is.EqualTo(_unicornEmu.Q[0].GetFloat(1)).Within(1).Ulps, "V0[1]");
Assert.That (_context.GetV(0).Extract<float>(2),
Is.EqualTo(_unicornEmu.Q[0].GetFloat(2)).Within(1).Ulps, "V0[2]");
Assert.That (_context.GetV(0).Extract<float>(3),
Is.EqualTo(_unicornEmu.Q[0].GetFloat(3)).Within(1).Ulps, "V0[3]");
});
Console.WriteLine(fpTolerances);
}
@ -440,10 +494,13 @@ namespace Ryujinx.Tests.Cpu
if (IsNormalOrSubnormalD(_unicornEmu.Q[0].AsDouble()) &&
IsNormalOrSubnormalD(_context.GetV(0).As<double>()))
{
Assert.That (_context.GetV(0).Extract<double>(0),
Is.EqualTo(_unicornEmu.Q[0].GetDouble(0)).Within(1).Ulps);
Assert.That (_context.GetV(0).Extract<double>(1),
Is.EqualTo(_unicornEmu.Q[0].GetDouble(1)).Within(1).Ulps);
Assert.Multiple(() =>
{
Assert.That (_context.GetV(0).Extract<double>(0),
Is.EqualTo(_unicornEmu.Q[0].GetDouble(0)).Within(1).Ulps, "V0[0]");
Assert.That (_context.GetV(0).Extract<double>(1),
Is.EqualTo(_unicornEmu.Q[0].GetDouble(1)).Within(1).Ulps, "V0[1]");
});
Console.WriteLine(fpTolerances);
}

View file

@ -4,15 +4,67 @@ using ARMeilleure.State;
using NUnit.Framework;
using System;
using System.Collections.Generic;
namespace Ryujinx.Tests.Cpu
{
[Category("Misc")]
public sealed class CpuTestMisc : CpuTest
{
#if Misc
#region "ValueSource (Types)"
private static IEnumerable<ulong> _1S_F_()
{
yield return 0x00000000FF7FFFFFul; // -Max Normal (float.MinValue)
yield return 0x0000000080800000ul; // -Min Normal
yield return 0x00000000807FFFFFul; // -Max Subnormal
yield return 0x0000000080000001ul; // -Min Subnormal (-float.Epsilon)
yield return 0x000000007F7FFFFFul; // +Max Normal (float.MaxValue)
yield return 0x0000000000800000ul; // +Min Normal
yield return 0x00000000007FFFFFul; // +Max Subnormal
yield return 0x0000000000000001ul; // +Min Subnormal (float.Epsilon)
if (!NoZeros)
{
yield return 0x0000000080000000ul; // -Zero
yield return 0x0000000000000000ul; // +Zero
}
if (!NoInfs)
{
yield return 0x00000000FF800000ul; // -Infinity
yield return 0x000000007F800000ul; // +Infinity
}
if (!NoNaNs)
{
yield return 0x00000000FFC00000ul; // -QNaN (all zeros payload) (float.NaN)
yield return 0x00000000FFBFFFFFul; // -SNaN (all ones payload)
yield return 0x000000007FC00000ul; // +QNaN (all zeros payload) (-float.NaN) (DefaultNaN)
yield return 0x000000007FBFFFFFul; // +SNaN (all ones payload)
}
for (int cnt = 1; cnt <= RndCnt; cnt++)
{
ulong grbg = TestContext.CurrentContext.Random.NextUInt();
ulong rnd1 = GenNormalS();
ulong rnd2 = GenSubnormalS();
yield return (grbg << 32) | rnd1;
yield return (grbg << 32) | rnd2;
}
}
#endregion
private const int RndCnt = 2;
private const int RndCntImm = 2;
private static readonly bool NoZeros = false;
private static readonly bool NoInfs = false;
private static readonly bool NoNaNs = false;
#region "AluImm & Csel"
[Test, Pairwise]
public void Adds_Csinc_64bit([Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul,
@ -357,6 +409,77 @@ namespace Ryujinx.Tests.Cpu
Assert.That(context.GetX(0), Is.EqualTo(a));
}
[Explicit]
[Test, Pairwise]
public void Misc4([ValueSource("_1S_F_")] ulong a,
[ValueSource("_1S_F_")] ulong b,
[ValueSource("_1S_F_")] ulong c,
[Values(0ul, 1ul, 2ul, 3ul)] ulong displacement)
{
if (!BitConverter.IsLittleEndian)
{
Assert.Ignore();
}
for (ulong gapOffset = 0; gapOffset < displacement; gapOffset++)
{
SetWorkingMemory(gapOffset, TestContext.CurrentContext.Random.NextByte());
}
SetWorkingMemory(0x0 + displacement, BitConverter.GetBytes((uint)b));
SetWorkingMemory(0x4 + displacement, BitConverter.GetBytes((uint)c));
SetWorkingMemory(0x8 + displacement, TestContext.CurrentContext.Random.NextByte());
SetWorkingMemory(0x9 + displacement, TestContext.CurrentContext.Random.NextByte());
SetWorkingMemory(0xA + displacement, TestContext.CurrentContext.Random.NextByte());
SetWorkingMemory(0xB + displacement, TestContext.CurrentContext.Random.NextByte());
SetContext(
x0: DataBaseAddress + displacement,
v0: MakeVectorE0E1(a, TestContext.CurrentContext.Random.NextULong()),
v1: MakeVectorE0E1(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong()),
v2: MakeVectorE0E1(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong()),
overflow: TestContext.CurrentContext.Random.NextBool(),
carry: TestContext.CurrentContext.Random.NextBool(),
zero: TestContext.CurrentContext.Random.NextBool(),
negative: TestContext.CurrentContext.Random.NextBool());
Opcode(0xBD400001); // LDR S1, [X0,#0]
Opcode(0xBD400402); // LDR S2, [X0,#4]
Opcode(0x1E215801); // FMIN S1, S0, S1
Opcode(0x1E222000); // FCMP S0, S2
Opcode(0x1E214C40); // FCSEL S0, S2, S1, MI
Opcode(0xBD000800); // STR S0, [X0,#8]
Opcode(0xD65F03C0); // RET
ExecuteOpcodes();
CompareAgainstUnicorn();
}
[Explicit]
[Test]
public void Misc5([ValueSource("_1S_F_")] ulong a)
{
SetContext(
v0: MakeVectorE0E1(a, TestContext.CurrentContext.Random.NextULong()),
v1: MakeVectorE0E1(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong()),
overflow: TestContext.CurrentContext.Random.NextBool(),
carry: TestContext.CurrentContext.Random.NextBool(),
zero: TestContext.CurrentContext.Random.NextBool(),
negative: TestContext.CurrentContext.Random.NextBool());
Opcode(0x1E202008); // FCMP S0, #0.0
Opcode(0x1E2E1001); // FMOV S1, #1.0
Opcode(0x1E215800); // FMIN S0, S0, S1
Opcode(0x1E2703E1); // FMOV S1, WZR
Opcode(0x1E204C20); // FCSEL S0, S1, S0, MI
Opcode(0xD65F03C0); // RET
ExecuteOpcodes();
CompareAgainstUnicorn();
}
#endif
}
}

View file

@ -918,7 +918,9 @@ namespace Ryujinx.Tests.Cpu
return new uint[]
{
0x6E30C800u, // FMAXNMV S0, V0.4S
0x6EB0C800u // FMINNMV S0, V0.4S
0x6E30F800u, // FMAXV S0, V0.4S
0x6EB0C800u, // FMINNMV S0, V0.4S
0x6EB0F800u // FMINV S0, V0.4S
};
}

View file

@ -373,12 +373,14 @@ namespace Ryujinx.Tests.Cpu
{
return new uint[]
{
0x0E20F400u, // FMAX V0.2S, V0.2S, V0.2S
0x0E20C400u, // FMAXNM V0.2S, V0.2S, V0.2S
0x2E20F400u, // FMAXP V0.2S, V0.2S, V0.2S
0x0EA0F400u, // FMIN V0.2S, V0.2S, V0.2S
0x0EA0C400u, // FMINNM V0.2S, V0.2S, V0.2S
0x2EA0F400u // FMINP V0.2S, V0.2S, V0.2S
0x0E20F400u, // FMAX V0.2S, V0.2S, V0.2S
0x0E20C400u, // FMAXNM V0.2S, V0.2S, V0.2S
0x2E20C400u, // FMAXNMP V0.2S, V0.2S, V0.2S
0x2E20F400u, // FMAXP V0.2S, V0.2S, V0.2S
0x0EA0F400u, // FMIN V0.2S, V0.2S, V0.2S
0x0EA0C400u, // FMINNM V0.2S, V0.2S, V0.2S
0x2EA0C400u, // FMINNMP V0.2S, V0.2S, V0.2S
0x2EA0F400u // FMINP V0.2S, V0.2S, V0.2S
};
}
@ -386,12 +388,14 @@ namespace Ryujinx.Tests.Cpu
{
return new uint[]
{
0x4E60F400u, // FMAX V0.2D, V0.2D, V0.2D
0x4E60C400u, // FMAXNM V0.2D, V0.2D, V0.2D
0x6E60F400u, // FMAXP V0.2D, V0.2D, V0.2D
0x4EE0F400u, // FMIN V0.2D, V0.2D, V0.2D
0x4EE0C400u, // FMINNM V0.2D, V0.2D, V0.2D
0x6EE0F400u // FMINP V0.2D, V0.2D, V0.2D
0x4E60F400u, // FMAX V0.2D, V0.2D, V0.2D
0x4E60C400u, // FMAXNM V0.2D, V0.2D, V0.2D
0x6E60C400u, // FMAXNMP V0.2D, V0.2D, V0.2D
0x6E60F400u, // FMAXP V0.2D, V0.2D, V0.2D
0x4EE0F400u, // FMIN V0.2D, V0.2D, V0.2D
0x4EE0C400u, // FMINNM V0.2D, V0.2D, V0.2D
0x6EE0C400u, // FMINNMP V0.2D, V0.2D, V0.2D
0x6EE0F400u // FMINP V0.2D, V0.2D, V0.2D
};
}
@ -531,6 +535,15 @@ namespace Ryujinx.Tests.Cpu
};
}
private static uint[] _ShlReg_S_D_()
{
return new uint[]
{
0x5EE04400u, // SSHL D0, D0, D0
0x7EE04400u // USHL D0, D0, D0
};
}
private static uint[] _ShlReg_V_8B_4H_2S_()
{
return new uint[]
@ -2820,6 +2833,26 @@ namespace Ryujinx.Tests.Cpu
CompareAgainstUnicorn();
}
[Test, Pairwise]
public void ShlReg_S_D([ValueSource("_ShlReg_S_D_")] uint opcodes,
[Values(0u)] uint rd,
[Values(1u, 0u)] uint rn,
[Values(2u, 0u)] uint rm,
[ValueSource("_1D_")] [Random(RndCnt)] ulong z,
[ValueSource("_1D_")] [Random(RndCnt)] ulong a,
[ValueSource("_1D_")] [Random(0ul, 255ul, RndCnt)] ulong b)
{
opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0);
V128 v0 = MakeVectorE0E1(z, z);
V128 v1 = MakeVectorE0(a);
V128 v2 = MakeVectorE0(b);
SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2);
CompareAgainstUnicorn(fpsrMask: Fpsr.Qc);
}
[Test, Pairwise]
public void ShlReg_V_8B_4H_2S([ValueSource("_ShlReg_V_8B_4H_2S_")] uint opcodes,
[Values(0u)] uint rd,