Compare commits
6 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
619ac86bd0 | ||
|
7a1ab71c73 | ||
|
dc4ba3993b | ||
|
81f1a4dc31 | ||
|
c64524a240 | ||
|
db45688aa8 |
@@ -251,6 +251,13 @@ namespace ARMeilleure.Decoders
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compare and branch instructions are always conditional.
|
||||
if (opCode.Instruction.Name == InstName.Cbz ||
|
||||
opCode.Instruction.Name == InstName.Cbnz)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Note: On ARM32, most instructions have conditional execution,
|
||||
// so there's no "Always" (unconditional) branch like on ARM64.
|
||||
// We need to check if the condition is "Always" instead.
|
||||
|
@@ -151,7 +151,7 @@ namespace ARMeilleure.Decoders
|
||||
|
||||
public static bool VectorArgumentsInvalid(bool q, params int[] args)
|
||||
{
|
||||
if (q)
|
||||
if (q)
|
||||
{
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
{
|
||||
|
@@ -3,6 +3,7 @@ namespace ARMeilleure.Decoders
|
||||
interface IOpCode32Mem : IOpCode32
|
||||
{
|
||||
int Rt { get; }
|
||||
int Rt2 => Rt | 1;
|
||||
int Rn { get; }
|
||||
|
||||
bool WBack { get; }
|
||||
|
12
ARMeilleure/Decoders/OpCode32SimdMovn.cs
Normal file
12
ARMeilleure/Decoders/OpCode32SimdMovn.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace ARMeilleure.Decoders
|
||||
{
|
||||
class OpCode32SimdMovn : OpCode32Simd
|
||||
{
|
||||
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMovn(inst, address, opCode);
|
||||
|
||||
public OpCode32SimdMovn(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
|
||||
{
|
||||
Size = (opCode >> 18) & 0x3;
|
||||
}
|
||||
}
|
||||
}
|
@@ -4,14 +4,13 @@ namespace ARMeilleure.Decoders
|
||||
{
|
||||
public int Rd { get; }
|
||||
|
||||
public bool Add => true;
|
||||
public int Immediate { get; }
|
||||
|
||||
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16Adr(inst, address, opCode);
|
||||
|
||||
public OpCodeT16Adr(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
|
||||
{
|
||||
Rd = (opCode >> 8) & 7;
|
||||
Rd = (opCode >> 8) & 7;
|
||||
|
||||
int imm = (opCode & 0xff) << 2;
|
||||
Immediate = (int)(GetPc() & 0xfffffffc) + imm;
|
||||
|
16
ARMeilleure/Decoders/OpCodeT32AluImm12.cs
Normal file
16
ARMeilleure/Decoders/OpCodeT32AluImm12.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace ARMeilleure.Decoders
|
||||
{
|
||||
class OpCodeT32AluImm12 : OpCodeT32Alu, IOpCode32AluImm
|
||||
{
|
||||
public int Immediate { get; }
|
||||
|
||||
public bool IsRotated => false;
|
||||
|
||||
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32AluImm12(inst, address, opCode);
|
||||
|
||||
public OpCodeT32AluImm12(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
|
||||
{
|
||||
Immediate = (opCode & 0xff) | ((opCode >> 4) & 0x700) | ((opCode >> 15) & 0x800);
|
||||
}
|
||||
}
|
||||
}
|
14
ARMeilleure/Decoders/OpCodeT32AluReg.cs
Normal file
14
ARMeilleure/Decoders/OpCodeT32AluReg.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace ARMeilleure.Decoders
|
||||
{
|
||||
class OpCodeT32AluReg : OpCodeT32Alu, IOpCode32AluReg
|
||||
{
|
||||
public int Rm { get; }
|
||||
|
||||
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32AluReg(inst, address, opCode);
|
||||
|
||||
public OpCodeT32AluReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
|
||||
{
|
||||
Rm = (opCode >> 0) & 0xf;
|
||||
}
|
||||
}
|
||||
}
|
@@ -23,7 +23,7 @@ namespace ARMeilleure.Decoders
|
||||
Add = ((opCode >> 23) & 1) != 0;
|
||||
WBack = ((opCode >> 21) & 1) != 0;
|
||||
|
||||
Immediate = opCode & 0xff;
|
||||
Immediate = (opCode & 0xff) << 2;
|
||||
|
||||
IsLoad = ((opCode >> 20) & 1) != 0;
|
||||
}
|
||||
|
19
ARMeilleure/Decoders/OpCodeT32MovImm16.cs
Normal file
19
ARMeilleure/Decoders/OpCodeT32MovImm16.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using ARMeilleure.Common;
|
||||
using System.Runtime.Intrinsics;
|
||||
|
||||
namespace ARMeilleure.Decoders
|
||||
{
|
||||
class OpCodeT32MovImm16 : OpCodeT32Alu, IOpCode32AluImm
|
||||
{
|
||||
public int Immediate { get; }
|
||||
|
||||
public bool IsRotated => false;
|
||||
|
||||
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32MovImm16(inst, address, opCode);
|
||||
|
||||
public OpCodeT32MovImm16(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
|
||||
{
|
||||
Immediate = (opCode & 0xff) | ((opCode >> 4) & 0x700) | ((opCode >> 15) & 0x800) | ((opCode >> 4) & 0xf000);
|
||||
}
|
||||
}
|
||||
}
|
19
ARMeilleure/Decoders/OpCodeT32ShiftReg.cs
Normal file
19
ARMeilleure/Decoders/OpCodeT32ShiftReg.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
namespace ARMeilleure.Decoders
|
||||
{
|
||||
class OpCodeT32ShiftReg : OpCodeT32Alu, IOpCode32AluRsReg
|
||||
{
|
||||
public int Rm => Rn;
|
||||
public int Rs { get; }
|
||||
|
||||
public ShiftType ShiftType { get; }
|
||||
|
||||
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32ShiftReg(inst, address, opCode);
|
||||
|
||||
public OpCodeT32ShiftReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
|
||||
{
|
||||
Rs = (opCode >> 0) & 0xf;
|
||||
|
||||
ShiftType = (ShiftType)((opCode >> 21) & 3);
|
||||
}
|
||||
}
|
||||
}
|
16
ARMeilleure/Decoders/OpCodeT32Tb.cs
Normal file
16
ARMeilleure/Decoders/OpCodeT32Tb.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace ARMeilleure.Decoders
|
||||
{
|
||||
class OpCodeT32Tb : OpCodeT32, IOpCode32BReg
|
||||
{
|
||||
public int Rm { get; }
|
||||
public int Rn { get; }
|
||||
|
||||
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32Tb(inst, address, opCode);
|
||||
|
||||
public OpCodeT32Tb(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
|
||||
{
|
||||
Rm = (opCode >> 0) & 0xf;
|
||||
Rn = (opCode >> 16) & 0xf;
|
||||
}
|
||||
}
|
||||
}
|
@@ -809,8 +809,8 @@ namespace ARMeilleure.Decoders
|
||||
SetA32("111100100xxxxxxxxxxx1000xxx0xxxx", InstName.Vadd, InstEmit32.Vadd_I, OpCode32SimdReg.Create);
|
||||
SetA32("<<<<11100x11xxxxxxxx101xx0x0xxxx", InstName.Vadd, InstEmit32.Vadd_S, OpCode32SimdRegS.Create);
|
||||
SetA32("111100100x00xxxxxxxx1101xxx0xxxx", InstName.Vadd, InstEmit32.Vadd_V, OpCode32SimdReg.Create);
|
||||
SetA32("1111001x1x<<xxxxxxxx0000x0x0xxxx", InstName.Vaddl, InstEmit32.Vaddl_I, OpCode32SimdRegLong.Create);
|
||||
SetA32("1111001x1x<<xxxxxxxx0001x0x0xxxx", InstName.Vaddw, InstEmit32.Vaddw_I, OpCode32SimdRegWide.Create);
|
||||
SetA32("1111001x1x<<xxxxxxx00000x0x0xxxx", InstName.Vaddl, InstEmit32.Vaddl_I, OpCode32SimdRegLong.Create);
|
||||
SetA32("1111001x1x<<xxxxxxx00001x0x0xxxx", InstName.Vaddw, InstEmit32.Vaddw_I, OpCode32SimdRegWide.Create);
|
||||
SetA32("111100100x00xxxxxxxx0001xxx1xxxx", InstName.Vand, InstEmit32.Vand_I, OpCode32SimdBinary.Create);
|
||||
SetA32("111100100x01xxxxxxxx0001xxx1xxxx", InstName.Vbic, InstEmit32.Vbic_I, OpCode32SimdBinary.Create);
|
||||
SetA32("1111001x1x000xxxxxxx<<x10x11xxxx", InstName.Vbic, InstEmit32.Vbic_II, OpCode32SimdImm.Create);
|
||||
@@ -879,6 +879,7 @@ namespace ARMeilleure.Decoders
|
||||
SetA32("111100100xxxxxxxxxxx1001xxx0xxxx", InstName.Vmla, InstEmit32.Vmla_I, OpCode32SimdReg.Create);
|
||||
SetA32("<<<<11100x00xxxxxxxx101xx0x0xxxx", InstName.Vmla, InstEmit32.Vmla_S, OpCode32SimdRegS.Create);
|
||||
SetA32("111100100x00xxxxxxxx1101xxx1xxxx", InstName.Vmla, InstEmit32.Vmla_V, OpCode32SimdReg.Create);
|
||||
SetA32("1111001x1x<<xxxxxxx01000x0x0xxxx", InstName.Vmlal, InstEmit32.Vmlal_I, OpCode32SimdRegLong.Create);
|
||||
SetA32("1111001x1x<<xxxxxxxx010xx1x0xxxx", InstName.Vmls, InstEmit32.Vmls_1, OpCode32SimdRegElem.Create);
|
||||
SetA32("<<<<11100x00xxxxxxxx101xx1x0xxxx", InstName.Vmls, InstEmit32.Vmls_S, OpCode32SimdRegS.Create);
|
||||
SetA32("111100100x10xxxxxxxx1101xxx1xxxx", InstName.Vmls, InstEmit32.Vmls_V, OpCode32SimdReg.Create);
|
||||
@@ -898,7 +899,7 @@ namespace ARMeilleure.Decoders
|
||||
SetA32("1111001x1x001000xxx0101000x1xxxx", InstName.Vmovl, InstEmit32.Vmovl, OpCode32SimdLong.Create);
|
||||
SetA32("1111001x1x010000xxx0101000x1xxxx", InstName.Vmovl, InstEmit32.Vmovl, OpCode32SimdLong.Create);
|
||||
SetA32("1111001x1x100000xxx0101000x1xxxx", InstName.Vmovl, InstEmit32.Vmovl, OpCode32SimdLong.Create);
|
||||
SetA32("111100111x11xx10xxxx001000x0xxx0", InstName.Vmovn, InstEmit32.Vmovn, OpCode32SimdCmpZ.Create);
|
||||
SetA32("111100111x11<<10xxxx001000x0xxx0", InstName.Vmovn, InstEmit32.Vmovn, OpCode32SimdMovn.Create);
|
||||
SetA32("<<<<11101111xxxxxxxx101000010000", InstName.Vmrs, InstEmit32.Vmrs, OpCode32SimdSpecial.Create);
|
||||
SetA32("<<<<11101110xxxxxxxx101000010000", InstName.Vmsr, InstEmit32.Vmsr, OpCode32SimdSpecial.Create);
|
||||
SetA32("1111001x1x<<xxxxxxxx100xx1x0xxxx", InstName.Vmul, InstEmit32.Vmul_1, OpCode32SimdRegElem.Create);
|
||||
@@ -924,22 +925,33 @@ namespace ARMeilleure.Decoders
|
||||
SetA32("1111001x1x000xxxxxxx<<x10x01xxxx", InstName.Vorr, InstEmit32.Vorr_II, OpCode32SimdImm.Create);
|
||||
SetA32("111100100x<<xxxxxxxx1011x0x1xxxx", InstName.Vpadd, InstEmit32.Vpadd_I, OpCode32SimdReg.Create);
|
||||
SetA32("111100110x00xxxxxxxx1101x0x0xxxx", InstName.Vpadd, InstEmit32.Vpadd_V, OpCode32SimdReg.Create);
|
||||
SetA32("111100111x11<<00xxxx0010xxx0xxxx", InstName.Vpaddl, InstEmit32.Vpaddl, OpCode32SimdCmpZ.Create);
|
||||
SetA32("1111001x0x<<xxxxxxxx1010x0x0xxxx", InstName.Vpmax, InstEmit32.Vpmax_I, OpCode32SimdReg.Create);
|
||||
SetA32("111100110x00xxxxxxxx1111x0x0xxxx", InstName.Vpmax, InstEmit32.Vpmax_V, OpCode32SimdReg.Create);
|
||||
SetA32("1111001x0x<<xxxxxxxx1010x0x1xxxx", InstName.Vpmin, InstEmit32.Vpmin_I, OpCode32SimdReg.Create);
|
||||
SetA32("111100110x10xxxxxxxx1111x0x0xxxx", InstName.Vpmin, InstEmit32.Vpmin_V, OpCode32SimdReg.Create);
|
||||
SetA32("1111001x0xxxxxxxxxxx0000xxx1xxxx", InstName.Vqadd, InstEmit32.Vqadd, OpCode32SimdReg.Create);
|
||||
SetA32("111100100x01xxxxxxxx1011xxx0xxxx", InstName.Vqdmulh, InstEmit32.Vqdmulh, OpCode32SimdReg.Create);
|
||||
SetA32("111100100x10xxxxxxxx1011xxx0xxxx", InstName.Vqdmulh, InstEmit32.Vqdmulh, OpCode32SimdReg.Create);
|
||||
SetA32("111100111x11<<10xxxx00101xx0xxx0", InstName.Vqmovn, InstEmit32.Vqmovn, OpCode32SimdMovn.Create);
|
||||
SetA32("111100111x11<<10xxxx001001x0xxx0", InstName.Vqmovun, InstEmit32.Vqmovun, OpCode32SimdMovn.Create);
|
||||
SetA32("1111001x1x>>>xxxxxxx100101x1xxx0", InstName.Vqrshrn, InstEmit32.Vqrshrn, OpCode32SimdShImmNarrow.Create);
|
||||
SetA32("111100111x>>>xxxxxxx100001x1xxx0", InstName.Vqrshrun, InstEmit32.Vqrshrun, OpCode32SimdShImmNarrow.Create);
|
||||
SetA32("1111001x1x>>>xxxxxxx100100x1xxx0", InstName.Vqshrn, InstEmit32.Vqshrn, OpCode32SimdShImmNarrow.Create);
|
||||
SetA32("111100111x>>>xxxxxxx100000x1xxx0", InstName.Vqshrun, InstEmit32.Vqshrun, OpCode32SimdShImmNarrow.Create);
|
||||
SetA32("1111001x0xxxxxxxxxxx0010xxx1xxxx", InstName.Vqsub, InstEmit32.Vqsub, OpCode32SimdReg.Create);
|
||||
SetA32("111100111x111011xxxx010x0xx0xxxx", InstName.Vrecpe, InstEmit32.Vrecpe, OpCode32SimdSqrte.Create);
|
||||
SetA32("111100100x00xxxxxxxx1111xxx1xxxx", InstName.Vrecps, InstEmit32.Vrecps, OpCode32SimdReg.Create);
|
||||
SetA32("111100111x11xx00xxxx000<<xx0xxxx", InstName.Vrev, InstEmit32.Vrev, OpCode32SimdRev.Create);
|
||||
SetA32("1111001x0x<<xxxxxxxx0001xxx0xxxx", InstName.Vrhadd, InstEmit32.Vrhadd, OpCode32SimdReg.Create);
|
||||
SetA32("111111101x1110xxxxxx101x01x0xxxx", InstName.Vrint, InstEmit32.Vrint_RM, OpCode32SimdS.Create);
|
||||
SetA32("<<<<11101x110110xxxx101x11x0xxxx", InstName.Vrint, InstEmit32.Vrint_Z, OpCode32SimdS.Create);
|
||||
SetA32("<<<<11101x110111xxxx101x01x0xxxx", InstName.Vrintx, InstEmit32.Vrintx_S, OpCode32SimdS.Create);
|
||||
SetA32("1111001x1x>>>xxxxxxx0010>xx1xxxx", InstName.Vrshr, InstEmit32.Vrshr, OpCode32SimdShImm.Create);
|
||||
SetA32("111100101x>>>xxxxxxx100001x1xxx0", InstName.Vrshrn, InstEmit32.Vrshrn, OpCode32SimdShImmNarrow.Create);
|
||||
SetA32("111100111x111011xxxx010x1xx0xxxx", InstName.Vrsqrte, InstEmit32.Vrsqrte, OpCode32SimdSqrte.Create);
|
||||
SetA32("111100100x10xxxxxxxx1111xxx1xxxx", InstName.Vrsqrts, InstEmit32.Vrsqrts, OpCode32SimdReg.Create);
|
||||
SetA32("1111001x1x>>>xxxxxxx0011>xx1xxxx", InstName.Vrsra, InstEmit32.Vrsra, OpCode32SimdShImm.Create);
|
||||
SetA32("111111100xxxxxxxxxxx101xx0x0xxxx", InstName.Vsel, InstEmit32.Vsel, OpCode32SimdSel.Create);
|
||||
SetA32("111100101x>>>xxxxxxx0101>xx1xxxx", InstName.Vshl, InstEmit32.Vshl, OpCode32SimdShImm.Create);
|
||||
SetA32("1111001x0xxxxxxxxxxx0100xxx0xxxx", InstName.Vshl, InstEmit32.Vshl_I, OpCode32SimdReg.Create);
|
||||
@@ -970,7 +982,8 @@ namespace ARMeilleure.Decoders
|
||||
SetA32("111100110xxxxxxxxxxx1000xxx0xxxx", InstName.Vsub, InstEmit32.Vsub_I, OpCode32SimdReg.Create);
|
||||
SetA32("<<<<11100x11xxxxxxxx101xx1x0xxxx", InstName.Vsub, InstEmit32.Vsub_S, OpCode32SimdRegS.Create);
|
||||
SetA32("111100100x10xxxxxxxx1101xxx0xxxx", InstName.Vsub, InstEmit32.Vsub_V, OpCode32SimdReg.Create);
|
||||
SetA32("1111001x1x<<xxxxxxxx0011x0x0xxxx", InstName.Vsubw, InstEmit32.Vsubw_I, OpCode32SimdRegWide.Create);
|
||||
SetA32("1111001x1x<<xxxxxxx00010x0x0xxxx", InstName.Vsubl, InstEmit32.Vsubl_I, OpCode32SimdRegLong.Create);
|
||||
SetA32("1111001x1x<<xxxxxxx00011x0x0xxxx", InstName.Vsubw, InstEmit32.Vsubw_I, OpCode32SimdRegWide.Create);
|
||||
SetA32("111100111x11xxxxxxxx10xxxxx0xxxx", InstName.Vtbl, InstEmit32.Vtbl, OpCode32SimdTbl.Create);
|
||||
SetA32("111100111x11<<10xxxx00001xx0xxxx", InstName.Vtrn, InstEmit32.Vtrn, OpCode32SimdCmpZ.Create);
|
||||
SetA32("111100100x<<xxxxxxxx1000xxx1xxxx", InstName.Vtst, InstEmit32.Vtst, OpCode32SimdReg.Create);
|
||||
@@ -1008,7 +1021,7 @@ namespace ARMeilleure.Decoders
|
||||
SetT16("01000101xxxxxxxx", InstName.Cmp, InstEmit32.Cmp, OpCodeT16AluRegHigh.Create);
|
||||
SetT16("01000110xxxxxxxx", InstName.Mov, InstEmit32.Mov, OpCodeT16AluRegHigh.Create);
|
||||
SetT16("010001110xxxx000", InstName.Bx, InstEmit32.Bx, OpCodeT16BReg.Create);
|
||||
SetT16("010001111xxxx000", InstName.Blx, InstEmit32.Blx, OpCodeT16BReg.Create);
|
||||
SetT16("010001111xxxx000", InstName.Blx, InstEmit32.Blxr, OpCodeT16BReg.Create);
|
||||
SetT16("01001xxxxxxxxxxx", InstName.Ldr, InstEmit32.Ldr, OpCodeT16MemLit.Create);
|
||||
SetT16("0101000xxxxxxxxx", InstName.Str, InstEmit32.Str, OpCodeT16MemReg.Create);
|
||||
SetT16("0101001xxxxxxxxx", InstName.Strh, InstEmit32.Strh, OpCodeT16MemReg.Create);
|
||||
@@ -1056,6 +1069,7 @@ namespace ARMeilleure.Decoders
|
||||
SetT32("11110x01010xxxxx0xxxxxxxxxxxxxxx", InstName.Adc, InstEmit32.Adc, OpCodeT32AluImm.Create);
|
||||
SetT32("11101011000<xxxx0xxx<<<<xxxxxxxx", InstName.Add, InstEmit32.Add, OpCodeT32AluRsImm.Create);
|
||||
SetT32("11110x01000<xxxx0xxx<<<<xxxxxxxx", InstName.Add, InstEmit32.Add, OpCodeT32AluImm.Create);
|
||||
SetT32("11110x100000xxxx0xxxxxxxxxxxxxxx", InstName.Add, InstEmit32.Add, OpCodeT32AluImm12.Create);
|
||||
SetT32("11101010000<xxxx0xxx<<<<xxxxxxxx", InstName.And, InstEmit32.And, OpCodeT32AluRsImm.Create);
|
||||
SetT32("11110x00000<xxxx0xxx<<<<xxxxxxxx", InstName.And, InstEmit32.And, OpCodeT32AluImm.Create);
|
||||
SetT32("11110x<<<xxxxxxx10x0xxxxxxxxxxxx", InstName.B, InstEmit32.B, OpCodeT32BImm20.Create);
|
||||
@@ -1064,12 +1078,14 @@ namespace ARMeilleure.Decoders
|
||||
SetT32("11110x00001xxxxx0xxxxxxxxxxxxxxx", InstName.Bic, InstEmit32.Bic, OpCodeT32AluImm.Create);
|
||||
SetT32("11110xxxxxxxxxxx11x1xxxxxxxxxxxx", InstName.Bl, InstEmit32.Bl, OpCodeT32BImm24.Create);
|
||||
SetT32("11110xxxxxxxxxxx11x0xxxxxxxxxxx0", InstName.Blx, InstEmit32.Blx, OpCodeT32BImm24.Create);
|
||||
SetT32("111110101011xxxx1111xxxx1000xxxx", InstName.Clz, InstEmit32.Clz, OpCodeT32AluReg.Create);
|
||||
SetT32("111010110001xxxx0xxx1111xxxxxxxx", InstName.Cmn, InstEmit32.Cmn, OpCodeT32AluRsImm.Create);
|
||||
SetT32("11110x010001xxxx0xxx1111xxxxxxxx", InstName.Cmn, InstEmit32.Cmn, OpCodeT32AluImm.Create);
|
||||
SetT32("111010111011xxxx0xxx1111xxxxxxxx", InstName.Cmp, InstEmit32.Cmp, OpCodeT32AluRsImm.Create);
|
||||
SetT32("11110x011011xxxx0xxx1111xxxxxxxx", InstName.Cmp, InstEmit32.Cmp, OpCodeT32AluImm.Create);
|
||||
SetT32("11101010100<xxxx0xxx<<<<xxxxxxxx", InstName.Eor, InstEmit32.Eor, OpCodeT32AluRsImm.Create);
|
||||
SetT32("11110x00100<xxxx0xxx<<<<xxxxxxxx", InstName.Eor, InstEmit32.Eor, OpCodeT32AluImm.Create);
|
||||
SetT32("111010001101xxxxxxxx111110101111", InstName.Lda, InstEmit32.Lda, OpCodeT32MemLdEx.Create);
|
||||
SetT32("111010001101xxxxxxxx111111101111", InstName.Ldaex, InstEmit32.Ldaex, OpCodeT32MemLdEx.Create);
|
||||
SetT32("1110100010x1xxxxxxxxxxxxxxxxxxxx", InstName.Ldm, InstEmit32.Ldm, OpCodeT32MemMult.Create);
|
||||
SetT32("1110100100x1xxxxxxxxxxxxxxxxxxxx", InstName.Ldm, InstEmit32.Ldm, OpCodeT32MemMult.Create);
|
||||
@@ -1082,7 +1098,8 @@ namespace ARMeilleure.Decoders
|
||||
SetT32("111110000001xxxx<<<<1100xxxxxxxx", InstName.Ldrb, InstEmit32.Ldrb, OpCodeT32MemImm8.Create);
|
||||
SetT32("111110000001xxxx<<<<11x1xxxxxxxx", InstName.Ldrb, InstEmit32.Ldrb, OpCodeT32MemImm8.Create);
|
||||
SetT32("111110001001xxxxxxxxxxxxxxxxxxxx", InstName.Ldrb, InstEmit32.Ldrb, OpCodeT32MemImm12.Create);
|
||||
SetT32("1110100>x1>1<<<<xxxxxxxxxxxxxxxx", InstName.Ldrd, InstEmit32.Ldrd, OpCodeT32MemImm8D.Create);
|
||||
SetT32("11101000x111<<<<xxxxxxxxxxxxxxxx", InstName.Ldrd, InstEmit32.Ldrd, OpCodeT32MemImm8D.Create);
|
||||
SetT32("11101001x1x1<<<<xxxxxxxxxxxxxxxx", InstName.Ldrd, InstEmit32.Ldrd, OpCodeT32MemImm8D.Create);
|
||||
SetT32("111110000011xxxx<<<<10x1xxxxxxxx", InstName.Ldrh, InstEmit32.Ldrh, OpCodeT32MemImm8.Create);
|
||||
SetT32("111110000011xxxx<<<<1100xxxxxxxx", InstName.Ldrh, InstEmit32.Ldrh, OpCodeT32MemImm8.Create);
|
||||
SetT32("111110000011xxxx<<<<11x1xxxxxxxx", InstName.Ldrh, InstEmit32.Ldrh, OpCodeT32MemImm8.Create);
|
||||
@@ -1096,9 +1113,12 @@ namespace ARMeilleure.Decoders
|
||||
SetT32("111110010011xxxx<<<<11x1xxxxxxxx", InstName.Ldrsh, InstEmit32.Ldrsh, OpCodeT32MemImm8.Create);
|
||||
SetT32("111110011011xxxxxxxxxxxxxxxxxxxx", InstName.Ldrsh, InstEmit32.Ldrsh, OpCodeT32MemImm12.Create);
|
||||
SetT32("11101010010x11110xxxxxxxxxxxxxxx", InstName.Mov, InstEmit32.Mov, OpCodeT32AluRsImm.Create);
|
||||
SetT32("111110100xxxxxxx1111xxxx0000xxxx", InstName.Mov, InstEmit32.Mov, OpCodeT32ShiftReg.Create);
|
||||
SetT32("11110x00010x11110xxxxxxxxxxxxxxx", InstName.Mov, InstEmit32.Mov, OpCodeT32AluImm.Create);
|
||||
SetT32("11110x100100xxxx0xxxxxxxxxxxxxxx", InstName.Mov, InstEmit32.Mov, OpCodeT32MovImm16.Create);
|
||||
SetT32("11101010011x11110xxxxxxxxxxxxxxx", InstName.Mvn, InstEmit32.Mvn, OpCodeT32AluRsImm.Create);
|
||||
SetT32("11110x00011x11110xxxxxxxxxxxxxxx", InstName.Mvn, InstEmit32.Mvn, OpCodeT32AluImm.Create);
|
||||
SetT32("11110011101011111000000000000000", InstName.Nop, InstEmit32.Nop, OpCodeT32.Create);
|
||||
SetT32("11101010011x<<<<0xxxxxxxxxxxxxxx", InstName.Orn, InstEmit32.Orn, OpCodeT32AluRsImm.Create);
|
||||
SetT32("11110x00011x<<<<0xxxxxxxxxxxxxxx", InstName.Orn, InstEmit32.Orn, OpCodeT32AluImm.Create);
|
||||
SetT32("11101010010x<<<<0xxxxxxxxxxxxxxx", InstName.Orr, InstEmit32.Orr, OpCodeT32AluRsImm.Create);
|
||||
@@ -1115,11 +1135,14 @@ namespace ARMeilleure.Decoders
|
||||
SetT32("111110000100<<<<xxxx000000xxxxxx", InstName.Str, InstEmit32.Str, OpCodeT32MemRsImm.Create);
|
||||
SetT32("111110000000xxxxxxxx1<<>xxxxxxxx", InstName.Strb, InstEmit32.Strb, OpCodeT32MemImm8.Create);
|
||||
SetT32("111110001000xxxxxxxxxxxxxxxxxxxx", InstName.Strb, InstEmit32.Strb, OpCodeT32MemImm12.Create);
|
||||
SetT32("1110100>x1>0<<<<xxxxxxxxxxxxxxxx", InstName.Strd, InstEmit32.Strd, OpCodeT32MemImm8D.Create);
|
||||
SetT32("11101000x110<<<<xxxxxxxxxxxxxxxx", InstName.Strd, InstEmit32.Strd, OpCodeT32MemImm8D.Create);
|
||||
SetT32("11101001x1x0<<<<xxxxxxxxxxxxxxxx", InstName.Strd, InstEmit32.Strd, OpCodeT32MemImm8D.Create);
|
||||
SetT32("111110000010xxxxxxxx1<<>xxxxxxxx", InstName.Strh, InstEmit32.Strh, OpCodeT32MemImm8.Create);
|
||||
SetT32("111110001010xxxxxxxxxxxxxxxxxxxx", InstName.Strh, InstEmit32.Strh, OpCodeT32MemImm12.Create);
|
||||
SetT32("11101011101<xxxx0xxx<<<<xxxxxxxx", InstName.Sub, InstEmit32.Sub, OpCodeT32AluRsImm.Create);
|
||||
SetT32("11110x01101<xxxx0xxx<<<<xxxxxxxx", InstName.Sub, InstEmit32.Sub, OpCodeT32AluImm.Create);
|
||||
SetT32("111010001101xxxx111100000000xxxx", InstName.Tbb, InstEmit32.Tbb, OpCodeT32Tb.Create);
|
||||
SetT32("111010001101xxxx111100000001xxxx", InstName.Tbh, InstEmit32.Tbh, OpCodeT32Tb.Create);
|
||||
SetT32("111010101001xxxx0xxx1111xxxxxxxx", InstName.Teq, InstEmit32.Teq, OpCodeT32AluRsImm.Create);
|
||||
SetT32("11110x001001xxxx0xxx1111xxxxxxxx", InstName.Teq, InstEmit32.Teq, OpCodeT32AluImm.Create);
|
||||
SetT32("111010100001xxxx0xxx1111xxxxxxxx", InstName.Tst, InstEmit32.Tst, OpCodeT32AluRsImm.Create);
|
||||
|
@@ -107,5 +107,30 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
context.SetIfThenBlockState(op.IfThenBlockConds);
|
||||
}
|
||||
|
||||
public static void Tbb(ArmEmitterContext context) => EmitTb(context, halfword: false);
|
||||
public static void Tbh(ArmEmitterContext context) => EmitTb(context, halfword: true);
|
||||
|
||||
private static void EmitTb(ArmEmitterContext context, bool halfword)
|
||||
{
|
||||
OpCodeT32Tb op = (OpCodeT32Tb)context.CurrOp;
|
||||
|
||||
Operand halfwords;
|
||||
|
||||
if (halfword)
|
||||
{
|
||||
Operand address = context.Add(GetIntA32(context, op.Rn), context.ShiftLeft(GetIntA32(context, op.Rm), Const(1)));
|
||||
halfwords = InstEmitMemoryHelper.EmitReadInt(context, address, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand address = context.Add(GetIntA32(context, op.Rn), GetIntA32(context, op.Rm));
|
||||
halfwords = InstEmitMemoryHelper.EmitReadIntAligned(context, address, 0);
|
||||
}
|
||||
|
||||
Operand targetAddress = context.Add(Const((int)op.GetPc()), context.ShiftLeft(halfwords, Const(1)));
|
||||
|
||||
EmitVirtualJump(context, targetAddress, isReturn: false);
|
||||
}
|
||||
}
|
||||
}
|
@@ -204,15 +204,15 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
context.BranchIfTrue(lblBigEndian, GetFlag(PState.EFlag));
|
||||
|
||||
Load(op.Rt, 0, WordSizeLog2);
|
||||
Load(op.Rt | 1, 4, WordSizeLog2);
|
||||
Load(op.Rt, 0, WordSizeLog2);
|
||||
Load(op.Rt2, 4, WordSizeLog2);
|
||||
|
||||
context.Branch(lblEnd);
|
||||
|
||||
context.MarkLabel(lblBigEndian);
|
||||
|
||||
Load(op.Rt | 1, 0, WordSizeLog2);
|
||||
Load(op.Rt, 4, WordSizeLog2);
|
||||
Load(op.Rt2, 0, WordSizeLog2);
|
||||
Load(op.Rt, 4, WordSizeLog2);
|
||||
|
||||
context.MarkLabel(lblEnd);
|
||||
}
|
||||
@@ -237,15 +237,15 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
context.BranchIfTrue(lblBigEndian, GetFlag(PState.EFlag));
|
||||
|
||||
Store(op.Rt, 0, WordSizeLog2);
|
||||
Store(op.Rt | 1, 4, WordSizeLog2);
|
||||
Store(op.Rt, 0, WordSizeLog2);
|
||||
Store(op.Rt2, 4, WordSizeLog2);
|
||||
|
||||
context.Branch(lblEnd);
|
||||
|
||||
context.MarkLabel(lblBigEndian);
|
||||
|
||||
Store(op.Rt | 1, 0, WordSizeLog2);
|
||||
Store(op.Rt, 4, WordSizeLog2);
|
||||
Store(op.Rt2, 0, WordSizeLog2);
|
||||
Store(op.Rt, 4, WordSizeLog2);
|
||||
|
||||
context.MarkLabel(lblEnd);
|
||||
}
|
||||
|
@@ -123,6 +123,41 @@ namespace ARMeilleure.Instructions
|
||||
context.CurrOp is OpCodeSimdMemSs);
|
||||
}
|
||||
|
||||
public static Operand EmitReadInt(ArmEmitterContext context, Operand address, int size)
|
||||
{
|
||||
Operand temp = context.AllocateLocal(size == 3 ? OperandType.I64 : OperandType.I32);
|
||||
|
||||
Operand lblSlowPath = Label();
|
||||
Operand lblEnd = Label();
|
||||
|
||||
Operand physAddr = EmitPtPointerLoad(context, address, lblSlowPath, write: false, size);
|
||||
|
||||
Operand value = default;
|
||||
|
||||
switch (size)
|
||||
{
|
||||
case 0: value = context.Load8 (physAddr); break;
|
||||
case 1: value = context.Load16(physAddr); break;
|
||||
case 2: value = context.Load (OperandType.I32, physAddr); break;
|
||||
case 3: value = context.Load (OperandType.I64, physAddr); break;
|
||||
}
|
||||
|
||||
context.Copy(temp, value);
|
||||
|
||||
if (!context.Memory.Type.IsHostMapped())
|
||||
{
|
||||
context.Branch(lblEnd);
|
||||
|
||||
context.MarkLabel(lblSlowPath, BasicBlockFrequency.Cold);
|
||||
|
||||
context.Copy(temp, EmitReadIntFallback(context, address, size));
|
||||
|
||||
context.MarkLabel(lblEnd);
|
||||
}
|
||||
|
||||
return temp;
|
||||
}
|
||||
|
||||
private static void EmitReadInt(ArmEmitterContext context, Operand address, int rt, int size)
|
||||
{
|
||||
Operand lblSlowPath = Label();
|
||||
@@ -419,6 +454,11 @@ namespace ARMeilleure.Instructions
|
||||
}
|
||||
|
||||
private static void EmitReadIntFallback(ArmEmitterContext context, Operand address, int rt, int size)
|
||||
{
|
||||
SetInt(context, rt, EmitReadIntFallback(context, address, size));
|
||||
}
|
||||
|
||||
private static Operand EmitReadIntFallback(ArmEmitterContext context, Operand address, int size)
|
||||
{
|
||||
MethodInfo info = null;
|
||||
|
||||
@@ -430,7 +470,7 @@ namespace ARMeilleure.Instructions
|
||||
case 3: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt64)); break;
|
||||
}
|
||||
|
||||
SetInt(context, rt, context.Call(info, address));
|
||||
return context.Call(info, address);
|
||||
}
|
||||
|
||||
private static void EmitReadVectorFallback(
|
||||
|
@@ -777,6 +777,13 @@ namespace ARMeilleure.Instructions
|
||||
}
|
||||
}
|
||||
|
||||
public static void Vmlal_I(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp;
|
||||
|
||||
EmitVectorTernaryLongOpI32(context, (d, n, m) => context.Add(d, context.Multiply(n, m)), !op.U);
|
||||
}
|
||||
|
||||
public static void Vmls_S(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.FastFP && Optimizations.UseSse2)
|
||||
@@ -992,6 +999,13 @@ namespace ARMeilleure.Instructions
|
||||
}
|
||||
}
|
||||
|
||||
public static void Vpaddl(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
|
||||
|
||||
EmitVectorPairwiseLongOpI32(context, (op1, op2) => context.Add(op1, op2), (op.Opc & 1) == 0);
|
||||
}
|
||||
|
||||
public static void Vpmax_V(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.FastFP && Optimizations.UseSse2)
|
||||
@@ -1014,7 +1028,7 @@ namespace ARMeilleure.Instructions
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitVectorPairwiseOpI32(context, (op1, op2) =>
|
||||
EmitVectorPairwiseOpI32(context, (op1, op2) =>
|
||||
{
|
||||
Operand greater = op.U ? context.ICompareGreaterUI(op1, op2) : context.ICompareGreater(op1, op2);
|
||||
return context.ConditionalSelect(greater, op1, op2);
|
||||
@@ -1052,6 +1066,62 @@ namespace ARMeilleure.Instructions
|
||||
}
|
||||
}
|
||||
|
||||
public static void Vqadd(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp;
|
||||
|
||||
EmitSaturatingAddSubBinaryOp(context, add: true, !op.U);
|
||||
}
|
||||
|
||||
public static void Vqdmulh(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp;
|
||||
int eSize = 8 << op.Size;
|
||||
|
||||
EmitVectorBinaryOpI32(context, (op1, op2) =>
|
||||
{
|
||||
if (op.Size == 2)
|
||||
{
|
||||
op1 = context.SignExtend32(OperandType.I64, op1);
|
||||
op2 = context.SignExtend32(OperandType.I64, op2);
|
||||
}
|
||||
|
||||
Operand res = context.Multiply(op1, op2);
|
||||
res = context.ShiftRightSI(res, Const(eSize - 1));
|
||||
res = EmitSatQ(context, res, eSize, signedSrc: true, signedDst: true);
|
||||
|
||||
if (op.Size == 2)
|
||||
{
|
||||
res = context.ConvertI64ToI32(res);
|
||||
}
|
||||
|
||||
return res;
|
||||
}, signed: true);
|
||||
}
|
||||
|
||||
public static void Vqmovn(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32SimdMovn op = (OpCode32SimdMovn)context.CurrOp;
|
||||
|
||||
bool signed = !op.Q;
|
||||
|
||||
EmitVectorUnaryNarrowOp32(context, (op1) => EmitSatQ(context, op1, 8 << op.Size, signed, signed), signed);
|
||||
}
|
||||
|
||||
public static void Vqmovun(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32SimdMovn op = (OpCode32SimdMovn)context.CurrOp;
|
||||
|
||||
EmitVectorUnaryNarrowOp32(context, (op1) => EmitSatQ(context, op1, 8 << op.Size, signedSrc: true, signedDst: false), signed: true);
|
||||
}
|
||||
|
||||
public static void Vqsub(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp;
|
||||
|
||||
EmitSaturatingAddSubBinaryOp(context, add: false, !op.U);
|
||||
}
|
||||
|
||||
public static void Vrev(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32SimdRev op = (OpCode32SimdRev)context.CurrOp;
|
||||
@@ -1202,6 +1272,30 @@ namespace ARMeilleure.Instructions
|
||||
}
|
||||
}
|
||||
|
||||
public static void Vrhadd(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp;
|
||||
|
||||
EmitVectorBinaryOpI32(context, (op1, op2) =>
|
||||
{
|
||||
if (op.Size == 2)
|
||||
{
|
||||
op1 = context.ZeroExtend32(OperandType.I64, op1);
|
||||
op2 = context.ZeroExtend32(OperandType.I64, op2);
|
||||
}
|
||||
|
||||
Operand res = context.Add(context.Add(op1, op2), Const(op1.Type, 1L));
|
||||
res = context.ShiftRightUI(res, Const(1));
|
||||
|
||||
if (op.Size == 2)
|
||||
{
|
||||
res = context.ConvertI64ToI32(res);
|
||||
}
|
||||
|
||||
return res;
|
||||
}, !op.U);
|
||||
}
|
||||
|
||||
public static void Vrsqrte(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32SimdSqrte op = (OpCode32SimdSqrte)context.CurrOp;
|
||||
@@ -1349,6 +1443,13 @@ namespace ARMeilleure.Instructions
|
||||
}
|
||||
}
|
||||
|
||||
public static void Vsubl_I(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32SimdRegLong op = (OpCode32SimdRegLong)context.CurrOp;
|
||||
|
||||
EmitVectorBinaryLongOpI32(context, (op1, op2) => context.Subtract(op1, op2), !op.U);
|
||||
}
|
||||
|
||||
public static void Vsubw_I(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32SimdRegWide op = (OpCode32SimdRegWide)context.CurrOp;
|
||||
@@ -1356,6 +1457,46 @@ namespace ARMeilleure.Instructions
|
||||
EmitVectorBinaryWideOpI32(context, (op1, op2) => context.Subtract(op1, op2), !op.U);
|
||||
}
|
||||
|
||||
private static void EmitSaturatingAddSubBinaryOp(ArmEmitterContext context, bool add, bool signed)
|
||||
{
|
||||
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
|
||||
|
||||
EmitVectorBinaryOpI32(context, (ne, me) =>
|
||||
{
|
||||
if (op.Size <= 2)
|
||||
{
|
||||
if (op.Size == 2)
|
||||
{
|
||||
ne = signed ? context.SignExtend32(OperandType.I64, ne) : context.ZeroExtend32(OperandType.I64, ne);
|
||||
me = signed ? context.SignExtend32(OperandType.I64, me) : context.ZeroExtend32(OperandType.I64, me);
|
||||
}
|
||||
|
||||
Operand res = add ? context.Add(ne, me) : context.Subtract(ne, me);
|
||||
|
||||
res = EmitSatQ(context, res, 8 << op.Size, signedSrc: true, signed);
|
||||
|
||||
if (op.Size == 2)
|
||||
{
|
||||
res = context.ConvertI64ToI32(res);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
else if (add) /* if (op.Size == 3) */
|
||||
{
|
||||
return signed
|
||||
? EmitBinarySignedSatQAdd(context, ne, me)
|
||||
: EmitBinaryUnsignedSatQAdd(context, ne, me);
|
||||
}
|
||||
else /* if (sub) */
|
||||
{
|
||||
return signed
|
||||
? EmitBinarySignedSatQSub(context, ne, me)
|
||||
: EmitBinaryUnsignedSatQSub(context, ne, me);
|
||||
}
|
||||
}, signed);
|
||||
}
|
||||
|
||||
private static void EmitSse41MaxMinNumOpF32(ArmEmitterContext context, bool isMaxNum, bool scalar)
|
||||
{
|
||||
IOpCode32Simd op = (IOpCode32Simd)context.CurrOp;
|
||||
|
@@ -1281,7 +1281,7 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
public static void EmitSseOrAvxExitFtzAndDazModesOpF(ArmEmitterContext context, Operand isTrue = default)
|
||||
{
|
||||
isTrue = isTrue == default
|
||||
isTrue = isTrue == default
|
||||
? context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFpcrFz)))
|
||||
: isTrue;
|
||||
|
||||
@@ -1612,7 +1612,7 @@ namespace ARMeilleure.Instructions
|
||||
}
|
||||
|
||||
// long BinarySignedSatQAdd(long op1, long op2);
|
||||
private static Operand EmitBinarySignedSatQAdd(ArmEmitterContext context, Operand op1, Operand op2)
|
||||
public static Operand EmitBinarySignedSatQAdd(ArmEmitterContext context, Operand op1, Operand op2)
|
||||
{
|
||||
Debug.Assert(op1.Type == OperandType.I64 && op2.Type == OperandType.I64);
|
||||
|
||||
@@ -1639,7 +1639,7 @@ namespace ARMeilleure.Instructions
|
||||
}
|
||||
|
||||
// ulong BinaryUnsignedSatQAdd(ulong op1, ulong op2);
|
||||
private static Operand EmitBinaryUnsignedSatQAdd(ArmEmitterContext context, Operand op1, Operand op2)
|
||||
public static Operand EmitBinaryUnsignedSatQAdd(ArmEmitterContext context, Operand op1, Operand op2)
|
||||
{
|
||||
Debug.Assert(op1.Type == OperandType.I64 && op2.Type == OperandType.I64);
|
||||
|
||||
@@ -1660,7 +1660,7 @@ namespace ARMeilleure.Instructions
|
||||
}
|
||||
|
||||
// long BinarySignedSatQSub(long op1, long op2);
|
||||
private static Operand EmitBinarySignedSatQSub(ArmEmitterContext context, Operand op1, Operand op2)
|
||||
public static Operand EmitBinarySignedSatQSub(ArmEmitterContext context, Operand op1, Operand op2)
|
||||
{
|
||||
Debug.Assert(op1.Type == OperandType.I64 && op2.Type == OperandType.I64);
|
||||
|
||||
@@ -1687,7 +1687,7 @@ namespace ARMeilleure.Instructions
|
||||
}
|
||||
|
||||
// ulong BinaryUnsignedSatQSub(ulong op1, ulong op2);
|
||||
private static Operand EmitBinaryUnsignedSatQSub(ArmEmitterContext context, Operand op1, Operand op2)
|
||||
public static Operand EmitBinaryUnsignedSatQSub(ArmEmitterContext context, Operand op1, Operand op2)
|
||||
{
|
||||
Debug.Assert(op1.Type == OperandType.I64 && op2.Type == OperandType.I64);
|
||||
|
||||
|
@@ -219,6 +219,25 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
// Integer
|
||||
|
||||
public static void EmitVectorUnaryAccumulateOpI32(ArmEmitterContext context, Func1I emit, bool signed)
|
||||
{
|
||||
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
|
||||
|
||||
Operand res = GetVecA32(op.Qd);
|
||||
|
||||
int elems = op.GetBytesCount() >> op.Size;
|
||||
|
||||
for (int index = 0; index < elems; index++)
|
||||
{
|
||||
Operand de = EmitVectorExtract32(context, op.Qd, op.Id + index, op.Size, signed);
|
||||
Operand me = EmitVectorExtract32(context, op.Qm, op.Im + index, op.Size, signed);
|
||||
|
||||
res = EmitVectorInsert(context, res, context.Add(de, emit(me)), op.Id + index, op.Size);
|
||||
}
|
||||
|
||||
context.Copy(GetVecA32(op.Qd), res);
|
||||
}
|
||||
|
||||
public static void EmitVectorUnaryOpI32(ArmEmitterContext context, Func1I emit, bool signed)
|
||||
{
|
||||
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
|
||||
@@ -385,6 +404,18 @@ namespace ARMeilleure.Instructions
|
||||
EmitVectorUnaryOpI32(context, emit, true);
|
||||
}
|
||||
|
||||
public static void EmitVectorUnaryOpSx32(ArmEmitterContext context, Func1I emit, bool accumulate)
|
||||
{
|
||||
if (accumulate)
|
||||
{
|
||||
EmitVectorUnaryAccumulateOpI32(context, emit, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitVectorUnaryOpI32(context, emit, true);
|
||||
}
|
||||
}
|
||||
|
||||
public static void EmitVectorBinaryOpSx32(ArmEmitterContext context, Func2I emit)
|
||||
{
|
||||
EmitVectorBinaryOpI32(context, emit, true);
|
||||
@@ -400,6 +431,18 @@ namespace ARMeilleure.Instructions
|
||||
EmitVectorUnaryOpI32(context, emit, false);
|
||||
}
|
||||
|
||||
public static void EmitVectorUnaryOpZx32(ArmEmitterContext context, Func1I emit, bool accumulate)
|
||||
{
|
||||
if (accumulate)
|
||||
{
|
||||
EmitVectorUnaryAccumulateOpI32(context, emit, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitVectorUnaryOpI32(context, emit, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void EmitVectorBinaryOpZx32(ArmEmitterContext context, Func2I emit)
|
||||
{
|
||||
EmitVectorBinaryOpI32(context, emit, false);
|
||||
@@ -592,6 +635,34 @@ namespace ARMeilleure.Instructions
|
||||
context.Copy(GetVecA32(op.Qd), res);
|
||||
}
|
||||
|
||||
public static void EmitVectorPairwiseLongOpI32(ArmEmitterContext context, Func2I emit, bool signed)
|
||||
{
|
||||
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
|
||||
|
||||
int elems = (op.Q ? 16 : 8) >> op.Size;
|
||||
int pairs = elems >> 1;
|
||||
int id = (op.Vd & 1) * pairs;
|
||||
|
||||
Operand res = GetVecA32(op.Qd);
|
||||
|
||||
for (int index = 0; index < pairs; index++)
|
||||
{
|
||||
int pairIndex = index << 1;
|
||||
Operand m1 = EmitVectorExtract32(context, op.Qm, op.Im + pairIndex, op.Size, signed);
|
||||
Operand m2 = EmitVectorExtract32(context, op.Qm, op.Im + pairIndex + 1, op.Size, signed);
|
||||
|
||||
if (op.Size == 2)
|
||||
{
|
||||
m1 = signed ? context.SignExtend32(OperandType.I64, m1) : context.ZeroExtend32(OperandType.I64, m1);
|
||||
m2 = signed ? context.SignExtend32(OperandType.I64, m2) : context.ZeroExtend32(OperandType.I64, m2);
|
||||
}
|
||||
|
||||
res = EmitVectorInsert(context, res, emit(m1, m2), id + index, op.Size + 1);
|
||||
}
|
||||
|
||||
context.Copy(GetVecA32(op.Qd), res);
|
||||
}
|
||||
|
||||
// Narrow
|
||||
|
||||
public static void EmitVectorUnaryNarrowOp32(ArmEmitterContext context, Func1I emit, bool signed = false)
|
||||
|
@@ -33,64 +33,24 @@ namespace ARMeilleure.Instructions
|
||||
EmitShrImmSaturatingNarrowOp(context, op.U ? ShrImmSaturatingNarrowFlags.VectorZxZx : ShrImmSaturatingNarrowFlags.VectorSxSx);
|
||||
}
|
||||
|
||||
public static void Vqshrun(ArmEmitterContext context)
|
||||
{
|
||||
EmitShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.VectorSxZx);
|
||||
}
|
||||
|
||||
public static void Vrshr(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp;
|
||||
int shift = GetImmShr(op);
|
||||
long roundConst = 1L << (shift - 1);
|
||||
EmitRoundShrImmOp(context, accumulate: false);
|
||||
}
|
||||
|
||||
if (op.U)
|
||||
{
|
||||
if (op.Size < 2)
|
||||
{
|
||||
EmitVectorUnaryOpZx32(context, (op1) =>
|
||||
{
|
||||
op1 = context.Add(op1, Const(op1.Type, roundConst));
|
||||
public static void Vrshrn(ArmEmitterContext context)
|
||||
{
|
||||
EmitRoundShrImmNarrowOp(context, signed: false);
|
||||
}
|
||||
|
||||
return context.ShiftRightUI(op1, Const(shift));
|
||||
});
|
||||
}
|
||||
else if (op.Size == 2)
|
||||
{
|
||||
EmitVectorUnaryOpZx32(context, (op1) =>
|
||||
{
|
||||
op1 = context.ZeroExtend32(OperandType.I64, op1);
|
||||
op1 = context.Add(op1, Const(op1.Type, roundConst));
|
||||
|
||||
return context.ConvertI64ToI32(context.ShiftRightUI(op1, Const(shift)));
|
||||
});
|
||||
}
|
||||
else /* if (op.Size == 3) */
|
||||
{
|
||||
EmitVectorUnaryOpZx32(context, (op1) => EmitShrImm64(context, op1, signed: false, roundConst, shift));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (op.Size < 2)
|
||||
{
|
||||
EmitVectorUnaryOpSx32(context, (op1) =>
|
||||
{
|
||||
op1 = context.Add(op1, Const(op1.Type, roundConst));
|
||||
|
||||
return context.ShiftRightSI(op1, Const(shift));
|
||||
});
|
||||
}
|
||||
else if (op.Size == 2)
|
||||
{
|
||||
EmitVectorUnaryOpSx32(context, (op1) =>
|
||||
{
|
||||
op1 = context.SignExtend32(OperandType.I64, op1);
|
||||
op1 = context.Add(op1, Const(op1.Type, roundConst));
|
||||
|
||||
return context.ConvertI64ToI32(context.ShiftRightSI(op1, Const(shift)));
|
||||
});
|
||||
}
|
||||
else /* if (op.Size == 3) */
|
||||
{
|
||||
EmitVectorUnaryOpZx32(context, (op1) => EmitShrImm64(context, op1, signed: true, roundConst, shift));
|
||||
}
|
||||
}
|
||||
public static void Vrsra(ArmEmitterContext context)
|
||||
{
|
||||
EmitRoundShrImmOp(context, accumulate: true);
|
||||
}
|
||||
|
||||
public static void Vshl(ArmEmitterContext context)
|
||||
@@ -191,6 +151,89 @@ namespace ARMeilleure.Instructions
|
||||
}
|
||||
}
|
||||
|
||||
public static void EmitRoundShrImmOp(ArmEmitterContext context, bool accumulate)
|
||||
{
|
||||
OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp;
|
||||
int shift = GetImmShr(op);
|
||||
long roundConst = 1L << (shift - 1);
|
||||
|
||||
if (op.U)
|
||||
{
|
||||
if (op.Size < 2)
|
||||
{
|
||||
EmitVectorUnaryOpZx32(context, (op1) =>
|
||||
{
|
||||
op1 = context.Add(op1, Const(op1.Type, roundConst));
|
||||
|
||||
return context.ShiftRightUI(op1, Const(shift));
|
||||
}, accumulate);
|
||||
}
|
||||
else if (op.Size == 2)
|
||||
{
|
||||
EmitVectorUnaryOpZx32(context, (op1) =>
|
||||
{
|
||||
op1 = context.ZeroExtend32(OperandType.I64, op1);
|
||||
op1 = context.Add(op1, Const(op1.Type, roundConst));
|
||||
|
||||
return context.ConvertI64ToI32(context.ShiftRightUI(op1, Const(shift)));
|
||||
}, accumulate);
|
||||
}
|
||||
else /* if (op.Size == 3) */
|
||||
{
|
||||
EmitVectorUnaryOpZx32(context, (op1) => EmitShrImm64(context, op1, signed: false, roundConst, shift), accumulate);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (op.Size < 2)
|
||||
{
|
||||
EmitVectorUnaryOpSx32(context, (op1) =>
|
||||
{
|
||||
op1 = context.Add(op1, Const(op1.Type, roundConst));
|
||||
|
||||
return context.ShiftRightSI(op1, Const(shift));
|
||||
}, accumulate);
|
||||
}
|
||||
else if (op.Size == 2)
|
||||
{
|
||||
EmitVectorUnaryOpSx32(context, (op1) =>
|
||||
{
|
||||
op1 = context.SignExtend32(OperandType.I64, op1);
|
||||
op1 = context.Add(op1, Const(op1.Type, roundConst));
|
||||
|
||||
return context.ConvertI64ToI32(context.ShiftRightSI(op1, Const(shift)));
|
||||
}, accumulate);
|
||||
}
|
||||
else /* if (op.Size == 3) */
|
||||
{
|
||||
EmitVectorUnaryOpZx32(context, (op1) => EmitShrImm64(context, op1, signed: true, roundConst, shift), accumulate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void EmitRoundShrImmNarrowOp(ArmEmitterContext context, bool signed)
|
||||
{
|
||||
OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp;
|
||||
|
||||
int shift = GetImmShr(op);
|
||||
long roundConst = 1L << (shift - 1);
|
||||
|
||||
EmitVectorUnaryNarrowOp32(context, (op1) =>
|
||||
{
|
||||
if (op.Size <= 1)
|
||||
{
|
||||
op1 = context.Add(op1, Const(op1.Type, roundConst));
|
||||
op1 = signed ? context.ShiftRightSI(op1, Const(shift)) : context.ShiftRightUI(op1, Const(shift));
|
||||
}
|
||||
else /* if (op.Size == 2 && round) */
|
||||
{
|
||||
op1 = EmitShrImm64(context, op1, signed, roundConst, shift); // shift <= 32
|
||||
}
|
||||
|
||||
return op1;
|
||||
}, signed);
|
||||
}
|
||||
|
||||
private static Operand EmitShlRegOp(ArmEmitterContext context, Operand op, Operand shiftLsB, int size, bool unsigned)
|
||||
{
|
||||
if (shiftLsB.Type == OperandType.I64)
|
||||
@@ -289,7 +332,7 @@ namespace ARMeilleure.Instructions
|
||||
op1 = EmitShrImm64(context, op1, signedSrc, roundConst, shift); // shift <= 32
|
||||
}
|
||||
|
||||
return EmitSatQ(context, op1, 8 << op.Size, signedDst);
|
||||
return EmitSatQ(context, op1, 8 << op.Size, signedSrc, signedDst);
|
||||
}, signedSrc);
|
||||
}
|
||||
|
||||
@@ -313,15 +356,20 @@ namespace ARMeilleure.Instructions
|
||||
return context.Call(info, value, Const(roundConst), Const(shift));
|
||||
}
|
||||
|
||||
private static Operand EmitSatQ(ArmEmitterContext context, Operand value, int eSize, bool signed)
|
||||
private static Operand EmitSatQ(ArmEmitterContext context, Operand value, int eSize, bool signedSrc, bool signedDst)
|
||||
{
|
||||
Debug.Assert(eSize <= 32);
|
||||
|
||||
long intMin = signed ? -(1L << (eSize - 1)) : 0;
|
||||
long intMax = signed ? (1L << (eSize - 1)) - 1 : (1L << eSize) - 1;
|
||||
long intMin = signedDst ? -(1L << (eSize - 1)) : 0;
|
||||
long intMax = signedDst ? (1L << (eSize - 1)) - 1 : (1L << eSize) - 1;
|
||||
|
||||
Operand gt = context.ICompareGreater(value, Const(value.Type, intMax));
|
||||
Operand lt = context.ICompareLess(value, Const(value.Type, intMin));
|
||||
Operand gt = signedSrc
|
||||
? context.ICompareGreater(value, Const(value.Type, intMax))
|
||||
: context.ICompareGreaterUI(value, Const(value.Type, intMax));
|
||||
|
||||
Operand lt = signedSrc
|
||||
? context.ICompareLess(value, Const(value.Type, intMin))
|
||||
: context.ICompareLessUI(value, Const(value.Type, intMin));
|
||||
|
||||
value = context.ConditionalSelect(gt, Const(value.Type, intMax), value);
|
||||
value = context.ConditionalSelect(lt, Const(value.Type, intMin), value);
|
||||
|
@@ -16,18 +16,13 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
OpCode32System op = (OpCode32System)context.CurrOp;
|
||||
|
||||
if (op.Coproc != 15)
|
||||
if (op.Coproc != 15 || op.Opc1 != 0)
|
||||
{
|
||||
InstEmit.Und(context);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (op.Opc1 != 0)
|
||||
{
|
||||
throw new NotImplementedException($"Unknown MRC Opc1 0x{op.Opc1:X16} at 0x{op.Address:X16}.");
|
||||
}
|
||||
|
||||
MethodInfo info;
|
||||
|
||||
switch (op.CRn)
|
||||
@@ -35,7 +30,7 @@ namespace ARMeilleure.Instructions
|
||||
case 13: // Process and Thread Info.
|
||||
if (op.CRm != 0)
|
||||
{
|
||||
throw new NotImplementedException($"Unknown MRC CRm 0x{op.CRm:X16} at 0x{op.Address:X16}.");
|
||||
throw new NotImplementedException($"Unknown MRC CRm 0x{op.CRm:X} at 0x{op.Address:X} (0x{op.RawOpCode:X}).");
|
||||
}
|
||||
|
||||
switch (op.Opc2)
|
||||
@@ -44,7 +39,7 @@ namespace ARMeilleure.Instructions
|
||||
info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.SetTpidrEl032)); break;
|
||||
|
||||
default:
|
||||
throw new NotImplementedException($"Unknown MRC Opc2 0x{op.Opc2:X16} at 0x{op.Address:X16}.");
|
||||
throw new NotImplementedException($"Unknown MRC Opc2 0x{op.Opc2:X} at 0x{op.Address:X} (0x{op.RawOpCode:X}).");
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -59,11 +54,11 @@ namespace ARMeilleure.Instructions
|
||||
return; // No-op.
|
||||
|
||||
default:
|
||||
throw new NotImplementedException($"Unknown MRC Opc2 0x{op.Opc2:X16} at 0x{op.Address:X16}.");
|
||||
throw new NotImplementedException($"Unknown MRC Opc2 0x{op.Opc2:X16} at 0x{op.Address:X16} (0x{op.RawOpCode:X}).");
|
||||
}
|
||||
|
||||
default:
|
||||
throw new NotImplementedException($"Unknown MRC CRm 0x{op.CRm:X16} at 0x{op.Address:X16}.");
|
||||
throw new NotImplementedException($"Unknown MRC CRm 0x{op.CRm:X16} at 0x{op.Address:X16} (0x{op.RawOpCode:X}).");
|
||||
}
|
||||
|
||||
default:
|
||||
@@ -77,18 +72,13 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
OpCode32System op = (OpCode32System)context.CurrOp;
|
||||
|
||||
if (op.Coproc != 15)
|
||||
if (op.Coproc != 15 || op.Opc1 != 0)
|
||||
{
|
||||
InstEmit.Und(context);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (op.Opc1 != 0)
|
||||
{
|
||||
throw new NotImplementedException($"Unknown MRC Opc1 0x{op.Opc1:X16} at 0x{op.Address:X16}.");
|
||||
}
|
||||
|
||||
MethodInfo info;
|
||||
|
||||
switch (op.CRn)
|
||||
@@ -96,7 +86,7 @@ namespace ARMeilleure.Instructions
|
||||
case 13: // Process and Thread Info.
|
||||
if (op.CRm != 0)
|
||||
{
|
||||
throw new NotImplementedException($"Unknown MRC CRm 0x{op.CRm:X16} at 0x{op.Address:X16}.");
|
||||
throw new NotImplementedException($"Unknown MRC CRm 0x{op.CRm:X} at 0x{op.Address:X} (0x{op.RawOpCode:X}).");
|
||||
}
|
||||
|
||||
switch (op.Opc2)
|
||||
@@ -108,13 +98,13 @@ namespace ARMeilleure.Instructions
|
||||
info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidr32)); break;
|
||||
|
||||
default:
|
||||
throw new NotImplementedException($"Unknown MRC Opc2 0x{op.Opc2:X16} at 0x{op.Address:X16}.");
|
||||
throw new NotImplementedException($"Unknown MRC Opc2 0x{op.Opc2:X} at 0x{op.Address:X} (0x{op.RawOpCode:X}).");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotImplementedException($"Unknown MRC 0x{op.RawOpCode:X8} at 0x{op.Address:X16}.");
|
||||
throw new NotImplementedException($"Unknown MRC 0x{op.RawOpCode:X} at 0x{op.Address:X}.");
|
||||
}
|
||||
|
||||
if (op.Rt == RegisterAlias.Aarch32Pc)
|
||||
@@ -154,13 +144,13 @@ namespace ARMeilleure.Instructions
|
||||
info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntpctEl0)); break;
|
||||
|
||||
default:
|
||||
throw new NotImplementedException($"Unknown MRRC Opc1 0x{opc:X16} at 0x{op.Address:X16}.");
|
||||
throw new NotImplementedException($"Unknown MRRC Opc1 0x{opc:X} at 0x{op.Address:X} (0x{op.RawOpCode:X}).");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotImplementedException($"Unknown MRRC 0x{op.RawOpCode:X8} at 0x{op.Address:X16}.");
|
||||
throw new NotImplementedException($"Unknown MRRC 0x{op.RawOpCode:X} at 0x{op.Address:X}.");
|
||||
}
|
||||
|
||||
Operand result = context.Call(info);
|
||||
@@ -265,7 +255,7 @@ namespace ARMeilleure.Instructions
|
||||
case 0b1000: // FPEXC
|
||||
throw new NotImplementedException("Supervisor Only");
|
||||
default:
|
||||
throw new NotImplementedException($"Unknown VMRS 0x{op.RawOpCode:X8} at 0x{op.Address:X16}.");
|
||||
throw new NotImplementedException($"Unknown VMRS 0x{op.RawOpCode:X} at 0x{op.Address:X}.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,7 +278,7 @@ namespace ARMeilleure.Instructions
|
||||
case 0b1000: // FPEXC
|
||||
throw new NotImplementedException("Supervisor Only");
|
||||
default:
|
||||
throw new NotImplementedException($"Unknown VMSR 0x{op.RawOpCode:X8} at 0x{op.Address:X16}.");
|
||||
throw new NotImplementedException($"Unknown VMSR 0x{op.RawOpCode:X} at 0x{op.Address:X}.");
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -545,6 +545,8 @@ namespace ARMeilleure.Instructions
|
||||
Strexh,
|
||||
Strh,
|
||||
Sxtb16,
|
||||
Tbb,
|
||||
Tbh,
|
||||
Teq,
|
||||
Trap,
|
||||
Tst,
|
||||
@@ -601,6 +603,7 @@ namespace ARMeilleure.Instructions
|
||||
Vmin,
|
||||
Vminnm,
|
||||
Vmla,
|
||||
Vmlal,
|
||||
Vmls,
|
||||
Vmlsl,
|
||||
Vmov,
|
||||
@@ -618,15 +621,24 @@ namespace ARMeilleure.Instructions
|
||||
Vorn,
|
||||
Vorr,
|
||||
Vpadd,
|
||||
Vpaddl,
|
||||
Vpmax,
|
||||
Vpmin,
|
||||
Vqadd,
|
||||
Vqdmulh,
|
||||
Vqmovn,
|
||||
Vqmovun,
|
||||
Vqrshrn,
|
||||
Vqrshrun,
|
||||
Vqshrn,
|
||||
Vqshrun,
|
||||
Vqsub,
|
||||
Vrev,
|
||||
Vrhadd,
|
||||
Vrint,
|
||||
Vrintx,
|
||||
Vrshr,
|
||||
Vrshrn,
|
||||
Vsel,
|
||||
Vshl,
|
||||
Vshll,
|
||||
@@ -643,8 +655,10 @@ namespace ARMeilleure.Instructions
|
||||
Vrecps,
|
||||
Vrsqrte,
|
||||
Vrsqrts,
|
||||
Vrsra,
|
||||
Vsra,
|
||||
Vsub,
|
||||
Vsubl,
|
||||
Vsubw,
|
||||
Vtbl,
|
||||
Vtrn,
|
||||
|
@@ -27,7 +27,7 @@ namespace ARMeilleure.Translation.PTC
|
||||
private const string OuterHeaderMagicString = "PTCohd\0\0";
|
||||
private const string InnerHeaderMagicString = "PTCihd\0\0";
|
||||
|
||||
private const uint InternalVersion = 3666; //! To be incremented manually for each change to the ARMeilleure project.
|
||||
private const uint InternalVersion = 3683; //! To be incremented manually for each change to the ARMeilleure project.
|
||||
|
||||
private const string ActualDir = "0";
|
||||
private const string BackupDir = "1";
|
||||
|
10
README.md
10
README.md
@@ -36,8 +36,8 @@
|
||||
|
||||
## Compatibility
|
||||
|
||||
As of January 2022, Ryujinx has been tested on approximately 3,500 titles; over 3,200 boot past menus and into gameplay, with roughly 2,500 of those being considered playable.
|
||||
You can check out the compatibility list [here](https://github.com/Ryujinx/Ryujinx-Games-List/issues). Anyone is free to submit an updated test on an existing game entry; simply follow the new issue template and testing guidelines, or post as a reply to the applicable game issue. Use the search function to see if a game has been tested already!
|
||||
As of September 2022, Ryujinx has been tested on approximately 3,600 titles; over 3,400 boot past menus and into gameplay, with roughly 2,700 of those being considered playable. You can check out the compatibility list [here](https://github.com/Ryujinx/Ryujinx-Games-List/issues).
|
||||
Anyone is free to submit a new game test or update an existing game test entry; simply follow the new issue template and testing guidelines, or post as a reply to the applicable game issue. Use the search function to see if a game has been tested already!
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -118,11 +118,11 @@ If you'd like to support the project financially, Ryujinx has an active Patreon
|
||||
<img src="https://images.squarespace-cdn.com/content/v1/560c1d39e4b0b4fae0c9cf2a/1567548955044-WVD994WZP76EWF15T0L3/Patreon+Button.png?format=500w" width="150">
|
||||
</a>
|
||||
|
||||
All the developers working on the project do so on their free time, but the project has several expenses:
|
||||
All developers working on the project do so in their free time, but the project has several expenses:
|
||||
* Hackable Nintendo Switch consoles to reverse-engineer the hardware
|
||||
* Additional computer hardware for testing purposes (e.g. GPUs to diagnose graphical bugs, etc.)
|
||||
* Licenses for various software development tools (e.g. Jetbrains, LDN servers, IDA)
|
||||
* Web hosting and infrastructure maintenance
|
||||
* Licenses for various software development tools (e.g. Jetbrains, IDA)
|
||||
* Web hosting and infrastructure maintenance (e.g. LDN servers)
|
||||
|
||||
All funds received through Patreon are considered a donation to support the project. Patrons receive early access to progress reports and exclusive access to developer interviews.
|
||||
|
||||
|
@@ -55,7 +55,6 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
private uint _processHandle;
|
||||
private ulong _appletResourceId;
|
||||
|
||||
private WritableRegion _workBufferRegion;
|
||||
private MemoryHandle _workBufferMemoryPin;
|
||||
|
||||
private Memory<float> _mixBuffer;
|
||||
@@ -98,7 +97,15 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
_sessionId = 0;
|
||||
}
|
||||
|
||||
public ResultCode Initialize(ref AudioRendererConfiguration parameter, uint processHandle, CpuAddress workBuffer, ulong workBufferSize, int sessionId, ulong appletResourceId, IVirtualMemoryManager memoryManager)
|
||||
public ResultCode Initialize(
|
||||
ref AudioRendererConfiguration parameter,
|
||||
uint processHandle,
|
||||
Memory<byte> workBufferMemory,
|
||||
CpuAddress workBuffer,
|
||||
ulong workBufferSize,
|
||||
int sessionId,
|
||||
ulong appletResourceId,
|
||||
IVirtualMemoryManager memoryManager)
|
||||
{
|
||||
if (!BehaviourContext.CheckValidRevision(parameter.Revision))
|
||||
{
|
||||
@@ -134,11 +141,10 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
|
||||
WorkBufferAllocator workBufferAllocator;
|
||||
|
||||
_workBufferRegion = MemoryManager.GetWritableRegion(workBuffer, (int)workBufferSize);
|
||||
_workBufferRegion.Memory.Span.Fill(0);
|
||||
_workBufferMemoryPin = _workBufferRegion.Memory.Pin();
|
||||
workBufferMemory.Span.Fill(0);
|
||||
_workBufferMemoryPin = workBufferMemory.Pin();
|
||||
|
||||
workBufferAllocator = new WorkBufferAllocator(_workBufferRegion.Memory);
|
||||
workBufferAllocator = new WorkBufferAllocator(workBufferMemory);
|
||||
|
||||
PoolMapper poolMapper = new PoolMapper(processHandle, false);
|
||||
poolMapper.InitializeSystemPool(ref _dspMemoryPoolState, workBuffer, workBufferSize);
|
||||
@@ -841,7 +847,6 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
_manager.Unregister(this);
|
||||
_terminationEvent.Dispose();
|
||||
_workBufferMemoryPin.Dispose();
|
||||
_workBufferRegion.Dispose();
|
||||
|
||||
if (MemoryManager is IRefCounted rc)
|
||||
{
|
||||
|
@@ -305,13 +305,34 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
/// <param name="workBufferSize">The guest work buffer size.</param>
|
||||
/// <param name="processHandle">The process handle of the application.</param>
|
||||
/// <returns>A <see cref="ResultCode"/> reporting an error or a success.</returns>
|
||||
public ResultCode OpenAudioRenderer(out AudioRenderSystem renderer, IVirtualMemoryManager memoryManager, ref AudioRendererConfiguration parameter, ulong appletResourceUserId, ulong workBufferAddress, ulong workBufferSize, uint processHandle, float volume)
|
||||
public ResultCode OpenAudioRenderer(
|
||||
out AudioRenderSystem renderer,
|
||||
IVirtualMemoryManager memoryManager,
|
||||
ref AudioRendererConfiguration parameter,
|
||||
ulong appletResourceUserId,
|
||||
ulong workBufferAddress,
|
||||
ulong workBufferSize,
|
||||
uint processHandle,
|
||||
float volume)
|
||||
{
|
||||
int sessionId = AcquireSessionId();
|
||||
|
||||
AudioRenderSystem audioRenderer = new AudioRenderSystem(this, _sessionsSystemEvent[sessionId]);
|
||||
|
||||
ResultCode result = audioRenderer.Initialize(ref parameter, processHandle, workBufferAddress, workBufferSize, sessionId, appletResourceUserId, memoryManager);
|
||||
// TODO: Eventually, we should try to use the guest supplied work buffer instead of allocating
|
||||
// our own. However, it was causing problems on some applications that would unmap the memory
|
||||
// before the audio renderer was fully disposed.
|
||||
Memory<byte> workBufferMemory = GC.AllocateArray<byte>((int)workBufferSize, pinned: true);
|
||||
|
||||
ResultCode result = audioRenderer.Initialize(
|
||||
ref parameter,
|
||||
processHandle,
|
||||
workBufferMemory,
|
||||
workBufferAddress,
|
||||
workBufferSize,
|
||||
sessionId,
|
||||
appletResourceUserId,
|
||||
memoryManager);
|
||||
|
||||
if (result == ResultCode.Success)
|
||||
{
|
||||
|
@@ -26,6 +26,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
public readonly bool SupportsNonConstantTextureOffset;
|
||||
public readonly bool SupportsShaderBallot;
|
||||
public readonly bool SupportsTextureShadowLod;
|
||||
public readonly bool SupportsViewportIndex;
|
||||
public readonly bool SupportsViewportSwizzle;
|
||||
public readonly bool SupportsIndirectParameters;
|
||||
|
||||
@@ -59,6 +60,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
bool supportsNonConstantTextureOffset,
|
||||
bool supportsShaderBallot,
|
||||
bool supportsTextureShadowLod,
|
||||
bool supportsViewportIndex,
|
||||
bool supportsViewportSwizzle,
|
||||
bool supportsIndirectParameters,
|
||||
uint maximumUniformBuffersPerStage,
|
||||
@@ -89,6 +91,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
SupportsNonConstantTextureOffset = supportsNonConstantTextureOffset;
|
||||
SupportsShaderBallot = supportsShaderBallot;
|
||||
SupportsTextureShadowLod = supportsTextureShadowLod;
|
||||
SupportsViewportIndex = supportsViewportIndex;
|
||||
SupportsViewportSwizzle = supportsViewportSwizzle;
|
||||
SupportsIndirectParameters = supportsIndirectParameters;
|
||||
MaximumUniformBuffersPerStage = maximumUniformBuffersPerStage;
|
||||
|
@@ -47,6 +47,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
public int TextureHandle;
|
||||
public int SamplerHandle;
|
||||
public Format ImageFormat;
|
||||
public int InvalidatedSequence;
|
||||
public Texture CachedTexture;
|
||||
public Sampler CachedSampler;
|
||||
@@ -564,6 +565,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
// Buffers are frequently re-created to accomodate larger data, so we need to re-bind
|
||||
// to ensure we're not using a old buffer that was already deleted.
|
||||
_channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, texture.Range.GetSubRange(0).Address, texture.Size, bindingInfo, bindingInfo.Format, false);
|
||||
|
||||
// Cache is not used for buffer texture, it must always rebind.
|
||||
state.CachedTexture = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -659,14 +663,16 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
cachedTexture?.SignalModified();
|
||||
}
|
||||
|
||||
if ((usageFlags & TextureUsageFlags.NeedsScaleValue) != 0 &&
|
||||
UpdateScale(state.CachedTexture, usageFlags, scaleIndex, stage))
|
||||
Format format = bindingInfo.Format == 0 ? cachedTexture.Format : bindingInfo.Format;
|
||||
|
||||
if (state.ImageFormat != format ||
|
||||
((usageFlags & TextureUsageFlags.NeedsScaleValue) != 0 &&
|
||||
UpdateScale(state.CachedTexture, usageFlags, scaleIndex, stage)))
|
||||
{
|
||||
ITexture hostTextureRebind = state.CachedTexture.GetTargetTexture(bindingInfo.Target);
|
||||
|
||||
Format format = bindingInfo.Format == 0 ? cachedTexture.Format : bindingInfo.Format;
|
||||
|
||||
state.Texture = hostTextureRebind;
|
||||
state.ImageFormat = format;
|
||||
|
||||
_context.Renderer.Pipeline.SetImage(bindingInfo.Binding, hostTextureRebind, format);
|
||||
}
|
||||
@@ -696,6 +702,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
|
||||
_channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, texture.Range.GetSubRange(0).Address, texture.Size, bindingInfo, format, true);
|
||||
|
||||
// Cache is not used for buffer texture, it must always rebind.
|
||||
state.CachedTexture = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -721,6 +730,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
format = texture.Format;
|
||||
}
|
||||
|
||||
state.ImageFormat = format;
|
||||
|
||||
_context.Renderer.Pipeline.SetImage(bindingInfo.Binding, hostTexture, format);
|
||||
}
|
||||
|
||||
|
@@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
private const ushort FileFormatVersionMajor = 1;
|
||||
private const ushort FileFormatVersionMinor = 2;
|
||||
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
|
||||
private const uint CodeGenVersion = 3672;
|
||||
private const uint CodeGenVersion = 3644;
|
||||
|
||||
private const string SharedTocFileName = "shared.toc";
|
||||
private const string SharedDataFileName = "shared.data";
|
||||
@@ -827,4 +827,4 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -134,6 +134,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
|
||||
public bool QueryHostSupportsTextureShadowLod() => _context.Capabilities.SupportsTextureShadowLod;
|
||||
|
||||
public bool QueryHostSupportsViewportIndex() => _context.Capabilities.SupportsViewportIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Converts a packed Maxwell texture format to the shader translator texture format.
|
||||
/// </summary>
|
||||
|
@@ -120,6 +120,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
supportsNonConstantTextureOffset: HwCapabilities.SupportsNonConstantTextureOffset,
|
||||
supportsShaderBallot: HwCapabilities.SupportsShaderBallot,
|
||||
supportsTextureShadowLod: HwCapabilities.SupportsTextureShadowLod,
|
||||
supportsViewportIndex: true,
|
||||
supportsViewportSwizzle: HwCapabilities.SupportsViewportSwizzle,
|
||||
supportsIndirectParameters: HwCapabilities.SupportsIndirectParameters,
|
||||
maximumUniformBuffersPerStage: 13, // TODO: Avoid hardcoding those limits here and get from driver?
|
||||
|
@@ -267,6 +267,15 @@ namespace Ryujinx.Graphics.Shader
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries host GPU shader viewport index output support.
|
||||
/// </summary>
|
||||
/// <returns>True if the GPU and driver supports shader viewport index output, false otherwise</returns>
|
||||
bool QueryHostSupportsViewportIndex()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries the point size from the GPU state, used when it is not explicitly set on the shader.
|
||||
/// </summary>
|
||||
|
@@ -78,6 +78,11 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
|
||||
public static bool Validate(ShaderConfig config, int value, bool isOutAttr)
|
||||
{
|
||||
if (value == AttributeConsts.ViewportIndex && !config.GpuAccessor.QueryHostSupportsViewportIndex())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return From(config, value, isOutAttr).IsValid;
|
||||
}
|
||||
|
||||
|
@@ -380,7 +380,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return BufferManager.GetData(buffer, offset, size);
|
||||
}
|
||||
|
||||
public Capabilities GetCapabilities()
|
||||
public unsafe Capabilities GetCapabilities()
|
||||
{
|
||||
FormatFeatureFlags compressedFormatFeatureFlags =
|
||||
FormatFeatureFlags.FormatFeatureSampledImageBit |
|
||||
@@ -409,7 +409,19 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
GAL.Format.Bc7Srgb,
|
||||
GAL.Format.Bc7Unorm);
|
||||
|
||||
Api.GetPhysicalDeviceFeatures(_physicalDevice, out var features);
|
||||
|
||||
PhysicalDeviceVulkan12Features featuresVk12 = new PhysicalDeviceVulkan12Features()
|
||||
{
|
||||
SType = StructureType.PhysicalDeviceVulkan12Features
|
||||
};
|
||||
|
||||
PhysicalDeviceFeatures2 features2 = new PhysicalDeviceFeatures2()
|
||||
{
|
||||
SType = StructureType.PhysicalDeviceFeatures2,
|
||||
PNext = &featuresVk12
|
||||
};
|
||||
|
||||
Api.GetPhysicalDeviceFeatures2(_physicalDevice, &features2);
|
||||
Api.GetPhysicalDeviceProperties(_physicalDevice, out var properties);
|
||||
|
||||
var limits = properties.Limits;
|
||||
@@ -419,7 +431,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
GpuVendor,
|
||||
hasFrontFacingBug: IsIntelWindows,
|
||||
hasVectorIndexingBug: Vendor == Vendor.Qualcomm,
|
||||
supportsAstcCompression: features.TextureCompressionAstcLdr,
|
||||
supportsAstcCompression: features2.Features.TextureCompressionAstcLdr,
|
||||
supportsBc123Compression: supportsBc123CompressionFormat,
|
||||
supportsBc45Compression: supportsBc45CompressionFormat,
|
||||
supportsBc67Compression: supportsBc67CompressionFormat,
|
||||
@@ -429,12 +441,13 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
supportsFragmentShaderInterlock: Capabilities.SupportsFragmentShaderInterlock,
|
||||
supportsFragmentShaderOrderingIntel: false,
|
||||
supportsGeometryShaderPassthrough: Capabilities.SupportsGeometryShaderPassthrough,
|
||||
supportsImageLoadFormatted: features.ShaderStorageImageReadWithoutFormat,
|
||||
supportsImageLoadFormatted: features2.Features.ShaderStorageImageReadWithoutFormat,
|
||||
supportsMismatchingViewFormat: true,
|
||||
supportsCubemapView: !IsAmdGcn,
|
||||
supportsNonConstantTextureOffset: false,
|
||||
supportsShaderBallot: false,
|
||||
supportsTextureShadowLod: false,
|
||||
supportsViewportIndex: featuresVk12.ShaderOutputViewportIndex,
|
||||
supportsViewportSwizzle: false,
|
||||
supportsIndirectParameters: Capabilities.SupportsIndirectParameters,
|
||||
maximumUniformBuffersPerStage: Constants.MaxUniformBuffersPerStage,
|
||||
|
@@ -31,11 +31,26 @@ namespace Ryujinx.HLE.HOS.Services.Audio
|
||||
return AudioRendererManagerImpl.GetWorkBufferSize(ref parameter);
|
||||
}
|
||||
|
||||
public ResultCode OpenAudioRenderer(ServiceCtx context, out IAudioRenderer obj, ref AudioRendererConfiguration parameter, ulong workBufferSize, ulong appletResourceUserId, KTransferMemory workBufferTransferMemory, uint processHandle)
|
||||
public ResultCode OpenAudioRenderer(
|
||||
ServiceCtx context,
|
||||
out IAudioRenderer obj,
|
||||
ref AudioRendererConfiguration parameter,
|
||||
ulong workBufferSize,
|
||||
ulong appletResourceUserId,
|
||||
KTransferMemory workBufferTransferMemory,
|
||||
uint processHandle)
|
||||
{
|
||||
var memoryManager = context.Process.HandleTable.GetKProcess((int)processHandle).CpuMemory;
|
||||
|
||||
ResultCode result = (ResultCode)_impl.OpenAudioRenderer(out AudioRenderSystem renderer, memoryManager, ref parameter, appletResourceUserId, workBufferTransferMemory.Address, workBufferTransferMemory.Size, processHandle, context.Device.Configuration.AudioVolume);
|
||||
ResultCode result = (ResultCode)_impl.OpenAudioRenderer(
|
||||
out AudioRenderSystem renderer,
|
||||
memoryManager,
|
||||
ref parameter,
|
||||
appletResourceUserId,
|
||||
workBufferTransferMemory.Address,
|
||||
workBufferTransferMemory.Size,
|
||||
processHandle,
|
||||
context.Device.Configuration.AudioVolume);
|
||||
|
||||
if (result == ResultCode.Success)
|
||||
{
|
||||
|
@@ -34,7 +34,14 @@ namespace Ryujinx.HLE.HOS.Services.Audio
|
||||
KTransferMemory workBufferTransferMemory = context.Process.HandleTable.GetObject<KTransferMemory>(transferMemoryHandle);
|
||||
uint processHandle = (uint)context.Request.HandleDesc.ToCopy[1];
|
||||
|
||||
ResultCode result = _impl.OpenAudioRenderer(context, out IAudioRenderer renderer, ref parameter, workBufferSize, appletResourceUserId, workBufferTransferMemory, processHandle);
|
||||
ResultCode result = _impl.OpenAudioRenderer(
|
||||
context,
|
||||
out IAudioRenderer renderer,
|
||||
ref parameter,
|
||||
workBufferSize,
|
||||
appletResourceUserId,
|
||||
workBufferTransferMemory,
|
||||
processHandle);
|
||||
|
||||
if (result == ResultCode.Success)
|
||||
{
|
||||
|
@@ -12,12 +12,22 @@ namespace Ryujinx.Tests.Cpu
|
||||
#if Simd32
|
||||
|
||||
#region "ValueSource (Opcodes)"
|
||||
private static uint[] _Vabs_Vneg_V_()
|
||||
private static uint[] _Vabs_Vneg_Vpaddl_I_()
|
||||
{
|
||||
return new uint[]
|
||||
{
|
||||
0xf3b10300u, // VABS.S8 D0, D0
|
||||
0xf3b10380u // VNEG.S8 D0, D0
|
||||
0xf3b10300u, // VABS.S8 D0, D0
|
||||
0xf3b10380u, // VNEG.S8 D0, D0
|
||||
0xf3b00200u // VPADDL.S8 D0, D0
|
||||
};
|
||||
}
|
||||
|
||||
private static uint[] _Vabs_Vneg_F_()
|
||||
{
|
||||
return new uint[]
|
||||
{
|
||||
0xf3b90700u, // VABS.F32 D0, D0
|
||||
0xf3b90780u // VNEG.F32 D0, D0
|
||||
};
|
||||
}
|
||||
#endregion
|
||||
@@ -201,40 +211,14 @@ namespace Ryujinx.Tests.Cpu
|
||||
}
|
||||
|
||||
[Test, Pairwise]
|
||||
public void Vabs_Vneg_V_S8_S16_S32([ValueSource("_Vabs_Vneg_V_")] uint opcode,
|
||||
[Range(0u, 3u)] uint rd,
|
||||
[Range(0u, 3u)] uint rm,
|
||||
[ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z,
|
||||
[ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b,
|
||||
[Values(0u, 1u, 2u)] uint size, // <S8, S16, S32>
|
||||
[Values] bool q)
|
||||
public void Vabs_Vneg_Vpaddl_V_I([ValueSource("_Vabs_Vneg_Vpaddl_I_")] uint opcode,
|
||||
[Range(0u, 3u)] uint rd,
|
||||
[Range(0u, 3u)] uint rm,
|
||||
[ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z,
|
||||
[ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b,
|
||||
[Values(0u, 1u, 2u)] uint size, // <S8, S16, S32>
|
||||
[Values] bool q)
|
||||
{
|
||||
const bool f = false;
|
||||
|
||||
Vabs_Vneg_V(opcode, rd, rm, z, b, size, f, q);
|
||||
}
|
||||
|
||||
[Test, Pairwise]
|
||||
public void Vabs_Vneg_V_F32([ValueSource("_Vabs_Vneg_V_")] uint opcode,
|
||||
[Range(0u, 3u)] uint rd,
|
||||
[Range(0u, 3u)] uint rm,
|
||||
[ValueSource("_2S_F_")] ulong z,
|
||||
[ValueSource("_2S_F_")] ulong b,
|
||||
[Values] bool q)
|
||||
{
|
||||
const uint size = 0b10; // <F32>
|
||||
const bool f = true;
|
||||
|
||||
Vabs_Vneg_V(opcode, rd, rm, z, b, size, f, q);
|
||||
}
|
||||
|
||||
private void Vabs_Vneg_V(uint opcode, uint rd, uint rm, ulong z, ulong b, uint size, bool f, bool q)
|
||||
{
|
||||
if (f)
|
||||
{
|
||||
opcode |= 1 << 10;
|
||||
}
|
||||
|
||||
if (q)
|
||||
{
|
||||
opcode |= 1 << 6;
|
||||
@@ -256,6 +240,33 @@ namespace Ryujinx.Tests.Cpu
|
||||
CompareAgainstUnicorn();
|
||||
}
|
||||
|
||||
[Test, Pairwise]
|
||||
public void Vabs_Vneg_V_F32([ValueSource("_Vabs_Vneg_F_")] uint opcode,
|
||||
[Range(0u, 3u)] uint rd,
|
||||
[Range(0u, 3u)] uint rm,
|
||||
[ValueSource("_2S_F_")] ulong z,
|
||||
[ValueSource("_2S_F_")] ulong b,
|
||||
[Values] bool q)
|
||||
{
|
||||
if (q)
|
||||
{
|
||||
opcode |= 1 << 6;
|
||||
|
||||
rd >>= 1; rd <<= 1;
|
||||
rm >>= 1; rm <<= 1;
|
||||
}
|
||||
|
||||
opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18);
|
||||
opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1);
|
||||
|
||||
V128 v0 = MakeVectorE0E1(z, ~z);
|
||||
V128 v1 = MakeVectorE0E1(b, ~b);
|
||||
|
||||
SingleOpcode(opcode, v0: v0, v1: v1);
|
||||
|
||||
CompareAgainstUnicorn();
|
||||
}
|
||||
|
||||
[Test, Pairwise, Description("VCNT.8 D0, D0 | VCNT.8 Q0, Q0")]
|
||||
public void Vcnt([Values(0u, 1u)] uint rd,
|
||||
[Values(0u, 1u)] uint rm,
|
||||
@@ -283,6 +294,32 @@ namespace Ryujinx.Tests.Cpu
|
||||
|
||||
CompareAgainstUnicorn();
|
||||
}
|
||||
|
||||
[Test, Pairwise]
|
||||
public void Vmovn_V([Range(0u, 3u)] uint rd,
|
||||
[Range(0u, 3u)] uint rm,
|
||||
[ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z,
|
||||
[ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b,
|
||||
[Values(0u, 1u, 2u, 3u)] uint op,
|
||||
[Values(0u, 1u, 2u)] uint size) // <S8, S16, S32>
|
||||
{
|
||||
rm >>= 1; rm <<= 1;
|
||||
|
||||
uint opcode = 0xf3b20200u; // VMOVN.S16 D0, Q0
|
||||
|
||||
opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18);
|
||||
opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1);
|
||||
|
||||
opcode |= (op & 0x3) << 6;
|
||||
opcode |= (size & 0x3) << 18;
|
||||
|
||||
V128 v0 = MakeVectorE0E1(z, ~z);
|
||||
V128 v1 = MakeVectorE0E1(b, ~b);
|
||||
|
||||
SingleOpcode(opcode, v0: v0, v1: v1);
|
||||
|
||||
CompareAgainstUnicorn();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
@@ -12,11 +12,13 @@ namespace Ryujinx.Tests.Cpu
|
||||
#if SimdReg32
|
||||
|
||||
#region "ValueSource (Opcodes)"
|
||||
private static uint[] _V_Add_Sub_Wide_I_()
|
||||
private static uint[] _V_Add_Sub_Long_Wide_I_()
|
||||
{
|
||||
return new uint[]
|
||||
{
|
||||
0xf2800000u, // VADDL.S8 Q0, D0, D0
|
||||
0xf2800100u, // VADDW.S8 Q0, Q0, D0
|
||||
0xf2800200u, // VSUBL.S8 Q0, D0, D0
|
||||
0xf2800300u // VSUBW.S8 Q0, Q0, D0
|
||||
};
|
||||
}
|
||||
@@ -74,6 +76,15 @@ namespace Ryujinx.Tests.Cpu
|
||||
};
|
||||
}
|
||||
|
||||
private static uint[] _Vmlal_Vmlsl_V_I_()
|
||||
{
|
||||
return new uint[]
|
||||
{
|
||||
0xf2800800u, // VMLAL.S8 Q0, D0, D0
|
||||
0xf2800a00u // VMLSL.S8 Q0, D0, D0
|
||||
};
|
||||
}
|
||||
|
||||
private static uint[] _Vp_Add_Max_Min_F_()
|
||||
{
|
||||
return new uint[]
|
||||
@@ -84,16 +95,30 @@ namespace Ryujinx.Tests.Cpu
|
||||
};
|
||||
}
|
||||
|
||||
// VPADD does not have an unsigned flag, so we check the opcode before setting it.
|
||||
private static uint VpaddI8 = 0xf2000b10u; // VPADD.I8 D0, D0, D0
|
||||
|
||||
private static uint[] _Vp_Add_Max_Min_I_()
|
||||
private static uint[] _Vp_Add_I_()
|
||||
{
|
||||
return new uint[]
|
||||
{
|
||||
VpaddI8,
|
||||
0xf2000a00u, // VPMAX.S8 D0, D0, D0
|
||||
0xf2000a10u // VPMIN.S8 D0, D0, D0
|
||||
0xf2000b10u // VPADD.I8 D0, D0, D0
|
||||
};
|
||||
}
|
||||
|
||||
private static uint[] _V_Pmax_Pmin_Rhadd_I_()
|
||||
{
|
||||
return new uint[]
|
||||
{
|
||||
0xf2000a00u, // VPMAX .S8 D0, D0, D0
|
||||
0xf2000a10u, // VPMIN .S8 D0, D0, D0
|
||||
0xf2000100u, // VRHADD.S8 D0, D0, D0
|
||||
};
|
||||
}
|
||||
|
||||
private static uint[] _Vq_Add_Sub_I_()
|
||||
{
|
||||
return new uint[]
|
||||
{
|
||||
0xf2000050u, // VQADD.S8 Q0, Q0, Q0
|
||||
0xf2000250u // VQSUB.S8 Q0, Q0, Q0
|
||||
};
|
||||
}
|
||||
#endregion
|
||||
@@ -350,7 +375,7 @@ namespace Ryujinx.Tests.Cpu
|
||||
|
||||
[Explicit]
|
||||
[Test, Pairwise, Description("VADD.f32 V0, V0, V0")]
|
||||
public void Vadd_f32([Values(0u)] uint rd,
|
||||
public void Vadd_F32([Values(0u)] uint rd,
|
||||
[Values(0u, 1u)] uint rn,
|
||||
[Values(0u, 2u)] uint rm,
|
||||
[ValueSource("_2S_F_")] ulong z0,
|
||||
@@ -384,15 +409,15 @@ namespace Ryujinx.Tests.Cpu
|
||||
}
|
||||
|
||||
[Test, Pairwise]
|
||||
public void V_Add_Sub_Wide_I([ValueSource("_V_Add_Sub_Wide_I_")] uint opcode,
|
||||
[Range(0u, 5u)] uint rd,
|
||||
[Range(0u, 5u)] uint rn,
|
||||
[Range(0u, 5u)] uint rm,
|
||||
[ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong z,
|
||||
[ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong a,
|
||||
[ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong b,
|
||||
[Values(0u, 1u, 2u)] uint size, // <SU8, SU16, SU32>
|
||||
[Values] bool u) // <S, U>
|
||||
public void V_Add_Sub_Long_Wide_I([ValueSource("_V_Add_Sub_Long_Wide_I_")] uint opcode,
|
||||
[Range(0u, 5u)] uint rd,
|
||||
[Range(0u, 5u)] uint rn,
|
||||
[Range(0u, 5u)] uint rm,
|
||||
[ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong z,
|
||||
[ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong a,
|
||||
[ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong b,
|
||||
[Values(0u, 1u, 2u)] uint size, // <SU8, SU16, SU32>
|
||||
[Values] bool u) // <S, U>
|
||||
{
|
||||
if (u)
|
||||
{
|
||||
@@ -566,18 +591,17 @@ namespace Ryujinx.Tests.Cpu
|
||||
CompareAgainstUnicorn();
|
||||
}
|
||||
|
||||
[Test, Pairwise, Description("VMLSL.<type><size> <Vd>, <Vn>, <Vm>")]
|
||||
public void Vmlsl_I([Values(0u)] uint rd,
|
||||
[Values(1u, 0u)] uint rn,
|
||||
[Values(2u, 0u)] uint rm,
|
||||
[Values(0u, 1u, 2u)] uint size,
|
||||
[Random(RndCnt)] ulong z,
|
||||
[Random(RndCnt)] ulong a,
|
||||
[Random(RndCnt)] ulong b,
|
||||
[Values] bool u)
|
||||
[Test, Pairwise]
|
||||
public void Vmlal_Vmlsl_I([ValueSource(nameof(_Vmlal_Vmlsl_V_I_))] uint opcode,
|
||||
[Values(0u)] uint rd,
|
||||
[Values(1u, 0u)] uint rn,
|
||||
[Values(2u, 0u)] uint rm,
|
||||
[Values(0u, 1u, 2u)] uint size,
|
||||
[Random(RndCnt)] ulong z,
|
||||
[Random(RndCnt)] ulong a,
|
||||
[Random(RndCnt)] ulong b,
|
||||
[Values] bool u)
|
||||
{
|
||||
uint opcode = 0xf2800a00u; // VMLSL.S8 Q0, D0, D0
|
||||
|
||||
opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1);
|
||||
opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18);
|
||||
opcode |= ((rn & 0xf) << 16) | ((rn & 0x10) << 3);
|
||||
@@ -736,17 +760,42 @@ namespace Ryujinx.Tests.Cpu
|
||||
}
|
||||
|
||||
[Test, Pairwise]
|
||||
public void Vp_Add_Max_Min_I([ValueSource("_Vp_Add_Max_Min_I_")] uint opcode,
|
||||
[Values(0u)] uint rd,
|
||||
[Range(0u, 5u)] uint rn,
|
||||
[Range(0u, 5u)] uint rm,
|
||||
[Values(0u, 1u, 2u)] uint size,
|
||||
[Random(RndCnt)] ulong z,
|
||||
[Random(RndCnt)] ulong a,
|
||||
[Random(RndCnt)] ulong b,
|
||||
[Values] bool u)
|
||||
public void Vp_Add_I([ValueSource("_Vp_Add_I_")] uint opcode,
|
||||
[Values(0u)] uint rd,
|
||||
[Range(0u, 5u)] uint rn,
|
||||
[Range(0u, 5u)] uint rm,
|
||||
[Values(0u, 1u, 2u)] uint size,
|
||||
[Random(RndCnt)] ulong z,
|
||||
[Random(RndCnt)] ulong a,
|
||||
[Random(RndCnt)] ulong b)
|
||||
{
|
||||
if (u && opcode != VpaddI8)
|
||||
opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1);
|
||||
opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18);
|
||||
opcode |= ((rn & 0xf) << 16) | ((rn & 0x10) << 3);
|
||||
|
||||
opcode |= size << 20;
|
||||
|
||||
V128 v0 = MakeVectorE0E1(z, z);
|
||||
V128 v1 = MakeVectorE0E1(a, z);
|
||||
V128 v2 = MakeVectorE0E1(b, z);
|
||||
|
||||
SingleOpcode(opcode, v0: v0, v1: v1, v2: v2);
|
||||
|
||||
CompareAgainstUnicorn();
|
||||
}
|
||||
|
||||
[Test, Pairwise]
|
||||
public void V_Pmax_Pmin_Rhadd_I([ValueSource("_V_Pmax_Pmin_Rhadd_I_")] uint opcode,
|
||||
[Values(0u)] uint rd,
|
||||
[Range(0u, 5u)] uint rn,
|
||||
[Range(0u, 5u)] uint rm,
|
||||
[Values(0u, 1u, 2u)] uint size,
|
||||
[Random(RndCnt)] ulong z,
|
||||
[Random(RndCnt)] ulong a,
|
||||
[Random(RndCnt)] ulong b,
|
||||
[Values] bool u)
|
||||
{
|
||||
if (u)
|
||||
{
|
||||
opcode |= 1 << 24;
|
||||
}
|
||||
@@ -765,6 +814,71 @@ namespace Ryujinx.Tests.Cpu
|
||||
|
||||
CompareAgainstUnicorn();
|
||||
}
|
||||
|
||||
[Test, Pairwise]
|
||||
public void Vq_Add_Sub_I([ValueSource("_Vq_Add_Sub_I_")] uint opcode,
|
||||
[Range(0u, 5u)] uint rd,
|
||||
[Range(0u, 5u)] uint rn,
|
||||
[Range(0u, 5u)] uint rm,
|
||||
[ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong z,
|
||||
[ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong a,
|
||||
[ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong b,
|
||||
[Values(0u, 1u, 2u)] uint size, // <SU8, SU16, SU32>
|
||||
[Values] bool u) // <S, U>
|
||||
{
|
||||
if (u)
|
||||
{
|
||||
opcode |= 1 << 24;
|
||||
}
|
||||
|
||||
rd >>= 1; rd <<= 1;
|
||||
rn >>= 1; rn <<= 1;
|
||||
rm >>= 1; rm <<= 1;
|
||||
|
||||
opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18);
|
||||
opcode |= ((rn & 0xf) << 16) | ((rn & 0x10) << 3);
|
||||
opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1);
|
||||
|
||||
opcode |= (size & 0x3) << 20;
|
||||
|
||||
V128 v0 = MakeVectorE0E1(z, ~z);
|
||||
V128 v1 = MakeVectorE0E1(a, ~a);
|
||||
V128 v2 = MakeVectorE0E1(b, ~b);
|
||||
|
||||
SingleOpcode(opcode, v0: v0, v1: v1, v2: v2);
|
||||
|
||||
CompareAgainstUnicorn();
|
||||
}
|
||||
|
||||
[Test, Pairwise, Description("VQDMULH.<S16, S32> <Qd>, <Qn>, <Qm>")]
|
||||
public void Vqdmulh_I([Range(0u, 5u)] uint rd,
|
||||
[Range(0u, 5u)] uint rn,
|
||||
[Range(0u, 5u)] uint rm,
|
||||
[ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong z,
|
||||
[ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong a,
|
||||
[ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong b,
|
||||
[Values(1u, 2u)] uint size) // <S16, S32>
|
||||
{
|
||||
rd >>= 1; rd <<= 1;
|
||||
rn >>= 1; rn <<= 1;
|
||||
rm >>= 1; rm <<= 1;
|
||||
|
||||
uint opcode = 0xf2100b40u & ~(3u << 20); // VQDMULH.S16 Q0, Q0, Q0
|
||||
|
||||
opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18);
|
||||
opcode |= ((rn & 0xf) << 16) | ((rn & 0x10) << 3);
|
||||
opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1);
|
||||
|
||||
opcode |= (size & 0x3) << 20;
|
||||
|
||||
V128 v0 = MakeVectorE0E1(z, ~z);
|
||||
V128 v1 = MakeVectorE0E1(a, ~a);
|
||||
V128 v2 = MakeVectorE0E1(b, ~b);
|
||||
|
||||
SingleOpcode(opcode, v0: v0, v1: v1, v2: v2);
|
||||
|
||||
CompareAgainstUnicorn();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
@@ -41,9 +41,10 @@ namespace Ryujinx.Tests.Cpu
|
||||
{
|
||||
return new uint[]
|
||||
{
|
||||
0xf2880010u, // VSHR.S8 D0, D0, #8
|
||||
0xf2880110u, // VSRA.S8 D0, D0, #8
|
||||
0xf2880210u, // VRSHR.S8 D0, D0, #8
|
||||
0xf2880010u // VSHR.S8 D0, D0, #8
|
||||
0xf2880310u // VRSRA.S8 D0, D0, #8
|
||||
};
|
||||
}
|
||||
|
||||
@@ -51,9 +52,10 @@ namespace Ryujinx.Tests.Cpu
|
||||
{
|
||||
return new uint[]
|
||||
{
|
||||
0xf2900010u, // VSHR.S16 D0, D0, #16
|
||||
0xf2900110u, // VSRA.S16 D0, D0, #16
|
||||
0xf2900210u, // VRSHR.S16 D0, D0, #16
|
||||
0xf2900010u // VSHR.S16 D0, D0, #16
|
||||
0xf2900310u // VRSRA.S16 D0, D0, #16
|
||||
};
|
||||
}
|
||||
|
||||
@@ -61,9 +63,10 @@ namespace Ryujinx.Tests.Cpu
|
||||
{
|
||||
return new uint[]
|
||||
{
|
||||
0xf2a00010u, // VSHR.S32 D0, D0, #32
|
||||
0xf2a00110u, // VSRA.S32 D0, D0, #32
|
||||
0xf2a00210u, // VRSHR.S32 D0, D0, #32
|
||||
0xf2a00010u // VSHR.S32 D0, D0, #32
|
||||
0xf2a00310u // VRSRA.S32 D0, D0, #32
|
||||
};
|
||||
}
|
||||
|
||||
@@ -76,6 +79,25 @@ namespace Ryujinx.Tests.Cpu
|
||||
0xf2800090u // VSHR.S64 D0, D0, #64
|
||||
};
|
||||
}
|
||||
|
||||
private static uint[] _Vqshrn_Vqrshrn_Vrshrn_Imm_()
|
||||
{
|
||||
return new uint[]
|
||||
{
|
||||
0xf2800910u, // VORR.I16 D0, #0 (immediate value changes it into QSHRN)
|
||||
0xf2800950u, // VORR.I16 Q0, #0 (immediate value changes it into QRSHRN)
|
||||
0xf2800850u // VMOV.I16 Q0, #0 (immediate value changes it into RSHRN)
|
||||
};
|
||||
}
|
||||
|
||||
private static uint[] _Vqshrun_Vqrshrun_Imm_()
|
||||
{
|
||||
return new uint[]
|
||||
{
|
||||
0xf3800810u, // VMOV.I16 D0, #0x80 (immediate value changes it into QSHRUN)
|
||||
0xf3800850u // VMOV.I16 Q0, #0x80 (immediate value changes it into QRSHRUN)
|
||||
};
|
||||
}
|
||||
#endregion
|
||||
|
||||
private const int RndCnt = 2;
|
||||
@@ -230,18 +252,17 @@ namespace Ryujinx.Tests.Cpu
|
||||
CompareAgainstUnicorn();
|
||||
}
|
||||
|
||||
[Test, Pairwise, Description("VQRSHRN.<type><size> <Vd>, <Vm>, #<imm>")]
|
||||
public void Vqrshrn_Imm([Values(0u, 1u)] uint rd,
|
||||
[Values(2u, 0u)] uint rm,
|
||||
[Values(0u, 1u, 2u)] uint size,
|
||||
[Random(RndCntShiftImm)] [Values(0u)] uint shiftImm,
|
||||
[Random(RndCnt)] ulong z,
|
||||
[Random(RndCnt)] ulong a,
|
||||
[Random(RndCnt)] ulong b,
|
||||
[Values] bool u)
|
||||
[Test, Pairwise]
|
||||
public void Vqshrn_Vqrshrn_Vrshrn_Imm([ValueSource("_Vqshrn_Vqrshrn_Vrshrn_Imm_")] uint opcode,
|
||||
[Values(0u, 1u)] uint rd,
|
||||
[Values(2u, 0u)] uint rm,
|
||||
[Values(0u, 1u, 2u)] uint size,
|
||||
[Random(RndCntShiftImm)] [Values(0u)] uint shiftImm,
|
||||
[Random(RndCnt)] ulong z,
|
||||
[Random(RndCnt)] ulong a,
|
||||
[Random(RndCnt)] ulong b,
|
||||
[Values] bool u)
|
||||
{
|
||||
uint opcode = 0xf2800950u; // VORR.I16 Q0, #0 (immediate value changes it into QRSHRN)
|
||||
|
||||
uint imm = 1u << ((int)size + 3);
|
||||
imm |= shiftImm & (imm - 1);
|
||||
|
||||
@@ -265,17 +286,16 @@ namespace Ryujinx.Tests.Cpu
|
||||
CompareAgainstUnicorn(fpsrMask: Fpsr.Qc);
|
||||
}
|
||||
|
||||
[Test, Pairwise, Description("VQRSHRUN.<type><size> <Vd>, <Vm>, #<imm>")]
|
||||
public void Vqrshrun_Imm([Values(0u, 1u)] uint rd,
|
||||
[Values(2u, 0u)] uint rm,
|
||||
[Values(0u, 1u, 2u)] uint size,
|
||||
[Random(RndCntShiftImm)] [Values(0u)] uint shiftImm,
|
||||
[Random(RndCnt)] ulong z,
|
||||
[Random(RndCnt)] ulong a,
|
||||
[Random(RndCnt)] ulong b)
|
||||
[Test, Pairwise]
|
||||
public void Vqshrun_Vqrshrun_Imm([ValueSource("_Vqshrun_Vqrshrun_Imm_")] uint opcode,
|
||||
[Values(0u, 1u)] uint rd,
|
||||
[Values(2u, 0u)] uint rm,
|
||||
[Values(0u, 1u, 2u)] uint size,
|
||||
[Random(RndCntShiftImm)] [Values(0u)] uint shiftImm,
|
||||
[Random(RndCnt)] ulong z,
|
||||
[Random(RndCnt)] ulong a,
|
||||
[Random(RndCnt)] ulong b)
|
||||
{
|
||||
uint opcode = 0xf3800850u; // VMOV.I16 Q0, #0x80 (immediate value changes it into QRSHRUN)
|
||||
|
||||
uint imm = 1u << ((int)size + 3);
|
||||
imm |= shiftImm & (imm - 1);
|
||||
|
||||
|
Reference in New Issue
Block a user