mirror of
https://github.com/Ryujinx/Ryujinx.git
synced 2024-08-19 21:52:22 +00:00
Add Tbx Inst. (fast & slow paths), with Tests. (#782)
* Update OpCodeTable.cs * Update InstName.cs * Update InstEmitSimdMove.cs * Update SoftFallback.cs * Update DelegateTypes.cs * Update CpuTestSimdTbl.cs * Update CpuTest.cs * Update Ryujinx.Tests.csproj * Nit.
This commit is contained in:
parent
92e5e3c505
commit
16869402bf
|
@ -518,6 +518,7 @@ namespace ARMeilleure.Decoders
|
||||||
SetA64("01011110xx100000001110xxxxxxxxxx", InstName.Suqadd_S, InstEmit.Suqadd_S, typeof(OpCodeSimd));
|
SetA64("01011110xx100000001110xxxxxxxxxx", InstName.Suqadd_S, InstEmit.Suqadd_S, typeof(OpCodeSimd));
|
||||||
SetA64("0>001110<<100000001110xxxxxxxxxx", InstName.Suqadd_V, InstEmit.Suqadd_V, typeof(OpCodeSimd));
|
SetA64("0>001110<<100000001110xxxxxxxxxx", InstName.Suqadd_V, InstEmit.Suqadd_V, typeof(OpCodeSimd));
|
||||||
SetA64("0x001110000xxxxx0xx000xxxxxxxxxx", InstName.Tbl_V, InstEmit.Tbl_V, typeof(OpCodeSimdTbl));
|
SetA64("0x001110000xxxxx0xx000xxxxxxxxxx", InstName.Tbl_V, InstEmit.Tbl_V, typeof(OpCodeSimdTbl));
|
||||||
|
SetA64("0x001110000xxxxx0xx100xxxxxxxxxx", InstName.Tbx_V, InstEmit.Tbx_V, typeof(OpCodeSimdTbl));
|
||||||
SetA64("0>001110<<0xxxxx001010xxxxxxxxxx", InstName.Trn1_V, InstEmit.Trn1_V, typeof(OpCodeSimdReg));
|
SetA64("0>001110<<0xxxxx001010xxxxxxxxxx", InstName.Trn1_V, InstEmit.Trn1_V, typeof(OpCodeSimdReg));
|
||||||
SetA64("0>001110<<0xxxxx011010xxxxxxxxxx", InstName.Trn2_V, InstEmit.Trn2_V, typeof(OpCodeSimdReg));
|
SetA64("0>001110<<0xxxxx011010xxxxxxxxxx", InstName.Trn2_V, InstEmit.Trn2_V, typeof(OpCodeSimdReg));
|
||||||
SetA64("0x101110<<1xxxxx011111xxxxxxxxxx", InstName.Uaba_V, InstEmit.Uaba_V, typeof(OpCodeSimdReg));
|
SetA64("0x101110<<1xxxxx011111xxxxxxxxxx", InstName.Uaba_V, InstEmit.Uaba_V, typeof(OpCodeSimdReg));
|
||||||
|
|
|
@ -61,11 +61,17 @@ namespace ARMeilleure.Instructions
|
||||||
|
|
||||||
delegate V128 _V128_U64(ulong a1);
|
delegate V128 _V128_U64(ulong a1);
|
||||||
delegate V128 _V128_V128(V128 a1);
|
delegate V128 _V128_V128(V128 a1);
|
||||||
|
delegate V128 _V128_V128_S32_V128(V128 a1, int a2, V128 a3);
|
||||||
|
delegate V128 _V128_V128_S32_V128_V128(V128 a1, int a2, V128 a3, V128 a4);
|
||||||
|
delegate V128 _V128_V128_S32_V128_V128_V128(V128 a1, int a2, V128 a3, V128 a4, V128 a5);
|
||||||
|
delegate V128 _V128_V128_S32_V128_V128_V128_V128(V128 a1, int a2, V128 a3, V128 a4, V128 a5, V128 a6);
|
||||||
delegate V128 _V128_V128_U32_V128(V128 a1, uint a2, V128 a3);
|
delegate V128 _V128_V128_U32_V128(V128 a1, uint a2, V128 a3);
|
||||||
delegate V128 _V128_V128_V128(V128 a1, V128 a2);
|
delegate V128 _V128_V128_V128(V128 a1, V128 a2);
|
||||||
|
delegate V128 _V128_V128_V128_S32_V128(V128 a1, V128 a2, int a3, V128 a4);
|
||||||
|
delegate V128 _V128_V128_V128_S32_V128_V128(V128 a1, V128 a2, int a3, V128 a4, V128 a5);
|
||||||
|
delegate V128 _V128_V128_V128_S32_V128_V128_V128(V128 a1, V128 a2, int a3, V128 a4, V128 a5, V128 a6);
|
||||||
|
delegate V128 _V128_V128_V128_S32_V128_V128_V128_V128(V128 a1, V128 a2, int a3, V128 a4, V128 a5, V128 a6, V128 a7);
|
||||||
delegate V128 _V128_V128_V128_V128(V128 a1, V128 a2, V128 a3);
|
delegate V128 _V128_V128_V128_V128(V128 a1, V128 a2, V128 a3);
|
||||||
delegate V128 _V128_V128_V128_V128_V128(V128 a1, V128 a2, V128 a3, V128 a4);
|
|
||||||
delegate V128 _V128_V128_V128_V128_V128_V128(V128 a1, V128 a2, V128 a3, V128 a4, V128 a5);
|
|
||||||
|
|
||||||
delegate void _Void();
|
delegate void _Void();
|
||||||
delegate void _Void_U64(ulong a1);
|
delegate void _Void_U64(ulong a1);
|
||||||
|
@ -75,4 +81,4 @@ namespace ARMeilleure.Instructions
|
||||||
delegate void _Void_U64_U64(ulong a1, ulong a2);
|
delegate void _Void_U64_U64(ulong a1, ulong a2);
|
||||||
delegate void _Void_U64_U8(ulong a1, byte a2);
|
delegate void _Void_U64_U8(ulong a1, byte a2);
|
||||||
delegate void _Void_U64_V128(ulong a1, V128 a2);
|
delegate void _Void_U64_V128(ulong a1, V128 a2);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ using ARMeilleure.Decoders;
|
||||||
using ARMeilleure.IntermediateRepresentation;
|
using ARMeilleure.IntermediateRepresentation;
|
||||||
using ARMeilleure.Translation;
|
using ARMeilleure.Translation;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
using static ARMeilleure.Instructions.InstEmitHelper;
|
using static ARMeilleure.Instructions.InstEmitHelper;
|
||||||
using static ARMeilleure.Instructions.InstEmitSimdHelper;
|
using static ARMeilleure.Instructions.InstEmitSimdHelper;
|
||||||
|
@ -384,79 +385,12 @@ namespace ARMeilleure.Instructions
|
||||||
|
|
||||||
public static void Tbl_V(ArmEmitterContext context)
|
public static void Tbl_V(ArmEmitterContext context)
|
||||||
{
|
{
|
||||||
OpCodeSimdTbl op = (OpCodeSimdTbl)context.CurrOp;
|
EmitTableVectorLookup(context, isTbl: true);
|
||||||
|
}
|
||||||
|
|
||||||
if (Optimizations.UseSsse3)
|
public static void Tbx_V(ArmEmitterContext context)
|
||||||
{
|
{
|
||||||
Operand n = GetVec(op.Rn);
|
EmitTableVectorLookup(context, isTbl: false);
|
||||||
Operand m = GetVec(op.Rm);
|
|
||||||
|
|
||||||
Operand mask = X86GetAllElements(context, 0x0F0F0F0F0F0F0F0FL);
|
|
||||||
|
|
||||||
Operand mMask = context.AddIntrinsic(Intrinsic.X86Pcmpgtb, m, mask);
|
|
||||||
|
|
||||||
mMask = context.AddIntrinsic(Intrinsic.X86Por, mMask, m);
|
|
||||||
|
|
||||||
Operand res = context.AddIntrinsic(Intrinsic.X86Pshufb, n, mMask);
|
|
||||||
|
|
||||||
for (int index = 1; index < op.Size; index++)
|
|
||||||
{
|
|
||||||
Operand ni = GetVec((op.Rn + index) & 0x1f);
|
|
||||||
|
|
||||||
Operand indexMask = X86GetAllElements(context, 0x1010101010101010L * index);
|
|
||||||
|
|
||||||
Operand mMinusMask = context.AddIntrinsic(Intrinsic.X86Psubb, m, indexMask);
|
|
||||||
|
|
||||||
Operand mMask2 = context.AddIntrinsic(Intrinsic.X86Pcmpgtb, mMinusMask, mask);
|
|
||||||
|
|
||||||
mMask2 = context.AddIntrinsic(Intrinsic.X86Por, mMask2, mMinusMask);
|
|
||||||
|
|
||||||
Operand res2 = context.AddIntrinsic(Intrinsic.X86Pshufb, ni, mMask2);
|
|
||||||
|
|
||||||
res = context.AddIntrinsic(Intrinsic.X86Por, res, res2);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (op.RegisterSize == RegisterSize.Simd64)
|
|
||||||
{
|
|
||||||
res = context.VectorZeroUpper64(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
context.Copy(GetVec(op.Rd), res);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Operand[] args = new Operand[1 + op.Size];
|
|
||||||
|
|
||||||
args[0] = GetVec(op.Rm);
|
|
||||||
|
|
||||||
for (int index = 0; index < op.Size; index++)
|
|
||||||
{
|
|
||||||
args[1 + index] = GetVec((op.Rn + index) & 0x1f);
|
|
||||||
}
|
|
||||||
|
|
||||||
Delegate dlg = null;
|
|
||||||
|
|
||||||
switch (op.Size)
|
|
||||||
{
|
|
||||||
case 1: dlg = op.RegisterSize == RegisterSize.Simd64
|
|
||||||
? (Delegate)new _V128_V128_V128(SoftFallback.Tbl1_V64)
|
|
||||||
: (Delegate)new _V128_V128_V128(SoftFallback.Tbl1_V128); break;
|
|
||||||
|
|
||||||
case 2: dlg = op.RegisterSize == RegisterSize.Simd64
|
|
||||||
? (Delegate)new _V128_V128_V128_V128(SoftFallback.Tbl2_V64)
|
|
||||||
: (Delegate)new _V128_V128_V128_V128(SoftFallback.Tbl2_V128); break;
|
|
||||||
|
|
||||||
case 3: dlg = op.RegisterSize == RegisterSize.Simd64
|
|
||||||
? (Delegate)new _V128_V128_V128_V128_V128(SoftFallback.Tbl3_V64)
|
|
||||||
: (Delegate)new _V128_V128_V128_V128_V128(SoftFallback.Tbl3_V128); break;
|
|
||||||
|
|
||||||
case 4: dlg = op.RegisterSize == RegisterSize.Simd64
|
|
||||||
? (Delegate)new _V128_V128_V128_V128_V128_V128(SoftFallback.Tbl4_V64)
|
|
||||||
: (Delegate)new _V128_V128_V128_V128_V128_V128(SoftFallback.Tbl4_V128); break;
|
|
||||||
}
|
|
||||||
|
|
||||||
context.Copy(GetVec(op.Rd), context.Call(dlg, args));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Trn1_V(ArmEmitterContext context)
|
public static void Trn1_V(ArmEmitterContext context)
|
||||||
|
@ -577,6 +511,116 @@ namespace ARMeilleure.Instructions
|
||||||
context.Copy(GetVec(op.Rd), mask);
|
context.Copy(GetVec(op.Rd), mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void EmitTableVectorLookup(ArmEmitterContext context, bool isTbl)
|
||||||
|
{
|
||||||
|
OpCodeSimdTbl op = (OpCodeSimdTbl)context.CurrOp;
|
||||||
|
|
||||||
|
if (Optimizations.UseSsse3)
|
||||||
|
{
|
||||||
|
Operand d = GetVec(op.Rd);
|
||||||
|
Operand m = GetVec(op.Rm);
|
||||||
|
|
||||||
|
Operand res;
|
||||||
|
|
||||||
|
Operand mask = X86GetAllElements(context, 0x0F0F0F0F0F0F0F0FL);
|
||||||
|
|
||||||
|
// Fast path for single register table.
|
||||||
|
{
|
||||||
|
Operand n = GetVec(op.Rn);
|
||||||
|
|
||||||
|
Operand mMask = context.AddIntrinsic(Intrinsic.X86Pcmpgtb, m, mask);
|
||||||
|
mMask = context.AddIntrinsic(Intrinsic.X86Por, mMask, m);
|
||||||
|
|
||||||
|
res = context.AddIntrinsic(Intrinsic.X86Pshufb, n, mMask);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int index = 1; index < op.Size; index++)
|
||||||
|
{
|
||||||
|
Operand ni = GetVec((op.Rn + index) & 0x1F);
|
||||||
|
|
||||||
|
Operand idxMask = X86GetAllElements(context, 0x1010101010101010L * index);
|
||||||
|
|
||||||
|
Operand mSubMask = context.AddIntrinsic(Intrinsic.X86Psubb, m, idxMask);
|
||||||
|
|
||||||
|
Operand mMask = context.AddIntrinsic(Intrinsic.X86Pcmpgtb, mSubMask, mask);
|
||||||
|
mMask = context.AddIntrinsic(Intrinsic.X86Por, mMask, mSubMask);
|
||||||
|
|
||||||
|
Operand res2 = context.AddIntrinsic(Intrinsic.X86Pshufb, ni, mMask);
|
||||||
|
|
||||||
|
res = context.AddIntrinsic(Intrinsic.X86Por, res, res2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isTbl)
|
||||||
|
{
|
||||||
|
Operand idxMask = X86GetAllElements(context, (0x1010101010101010L * op.Size) - 0x0101010101010101L);
|
||||||
|
Operand zeroMask = context.VectorZero();
|
||||||
|
|
||||||
|
Operand mPosMask = context.AddIntrinsic(Intrinsic.X86Pcmpgtb, m, idxMask);
|
||||||
|
Operand mNegMask = context.AddIntrinsic(Intrinsic.X86Pcmpgtb, zeroMask, m);
|
||||||
|
|
||||||
|
Operand mMask = context.AddIntrinsic(Intrinsic.X86Por, mPosMask, mNegMask);
|
||||||
|
|
||||||
|
Operand dMask = context.AddIntrinsic(Intrinsic.X86Pand, d, mMask);
|
||||||
|
|
||||||
|
res = context.AddIntrinsic(Intrinsic.X86Por, res, dMask);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (op.RegisterSize == RegisterSize.Simd64)
|
||||||
|
{
|
||||||
|
res = context.VectorZeroUpper64(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Copy(d, res);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Operand d = GetVec(op.Rd);
|
||||||
|
|
||||||
|
List<Operand> args = new List<Operand>();
|
||||||
|
|
||||||
|
if (!isTbl)
|
||||||
|
{
|
||||||
|
args.Add(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
args.Add(GetVec(op.Rm));
|
||||||
|
|
||||||
|
args.Add(Const(op.RegisterSize == RegisterSize.Simd64 ? 8 : 16));
|
||||||
|
|
||||||
|
for (int index = 0; index < op.Size; index++)
|
||||||
|
{
|
||||||
|
args.Add(GetVec((op.Rn + index) & 0x1F));
|
||||||
|
}
|
||||||
|
|
||||||
|
Delegate dlg = null;
|
||||||
|
|
||||||
|
switch (op.Size)
|
||||||
|
{
|
||||||
|
case 1: dlg = isTbl
|
||||||
|
? (Delegate)new _V128_V128_S32_V128 (SoftFallback.Tbl1)
|
||||||
|
: (Delegate)new _V128_V128_V128_S32_V128(SoftFallback.Tbx1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2: dlg = isTbl
|
||||||
|
? (Delegate)new _V128_V128_S32_V128_V128 (SoftFallback.Tbl2)
|
||||||
|
: (Delegate)new _V128_V128_V128_S32_V128_V128(SoftFallback.Tbx2);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3: dlg = isTbl
|
||||||
|
? (Delegate)new _V128_V128_S32_V128_V128_V128 (SoftFallback.Tbl3)
|
||||||
|
: (Delegate)new _V128_V128_V128_S32_V128_V128_V128(SoftFallback.Tbx3);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 4: dlg = isTbl
|
||||||
|
? (Delegate)new _V128_V128_S32_V128_V128_V128_V128 (SoftFallback.Tbl4)
|
||||||
|
: (Delegate)new _V128_V128_V128_S32_V128_V128_V128_V128(SoftFallback.Tbx4);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Copy(d, context.Call(dlg, args.ToArray()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void EmitVectorTranspose(ArmEmitterContext context, int part)
|
private static void EmitVectorTranspose(ArmEmitterContext context, int part)
|
||||||
{
|
{
|
||||||
OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp;
|
OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp;
|
||||||
|
@ -791,4 +835,4 @@ namespace ARMeilleure.Instructions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -375,6 +375,7 @@ namespace ARMeilleure.Instructions
|
||||||
Suqadd_S,
|
Suqadd_S,
|
||||||
Suqadd_V,
|
Suqadd_V,
|
||||||
Tbl_V,
|
Tbl_V,
|
||||||
|
Tbx_V,
|
||||||
Trn1_V,
|
Trn1_V,
|
||||||
Trn2_V,
|
Trn2_V,
|
||||||
Uaba_V,
|
Uaba_V,
|
||||||
|
@ -456,4 +457,4 @@ namespace ARMeilleure.Instructions
|
||||||
Strd,
|
Strd,
|
||||||
Strh
|
Strh
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -837,49 +837,55 @@ namespace ARMeilleure.Instructions
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region "Table"
|
#region "Table"
|
||||||
public static V128 Tbl1_V64(V128 vector, V128 tb0)
|
public static V128 Tbl1(V128 vector, int bytes, V128 tb0)
|
||||||
{
|
{
|
||||||
return Tbl(vector, 8, tb0);
|
return TblOrTbx(default, vector, bytes, tb0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static V128 Tbl1_V128(V128 vector, V128 tb0)
|
public static V128 Tbl2(V128 vector, int bytes, V128 tb0, V128 tb1)
|
||||||
{
|
{
|
||||||
return Tbl(vector, 16, tb0);
|
return TblOrTbx(default, vector, bytes, tb0, tb1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static V128 Tbl2_V64(V128 vector, V128 tb0, V128 tb1)
|
public static V128 Tbl3(V128 vector, int bytes, V128 tb0, V128 tb1, V128 tb2)
|
||||||
{
|
{
|
||||||
return Tbl(vector, 8, tb0, tb1);
|
return TblOrTbx(default, vector, bytes, tb0, tb1, tb2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static V128 Tbl2_V128(V128 vector, V128 tb0, V128 tb1)
|
public static V128 Tbl4(V128 vector, int bytes, V128 tb0, V128 tb1, V128 tb2, V128 tb3)
|
||||||
{
|
{
|
||||||
return Tbl(vector, 16, tb0, tb1);
|
return TblOrTbx(default, vector, bytes, tb0, tb1, tb2, tb3);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static V128 Tbl3_V64(V128 vector, V128 tb0, V128 tb1, V128 tb2)
|
public static V128 Tbx1(V128 dest, V128 vector, int bytes, V128 tb0)
|
||||||
{
|
{
|
||||||
return Tbl(vector, 8, tb0, tb1, tb2);
|
return TblOrTbx(dest, vector, bytes, tb0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static V128 Tbl3_V128(V128 vector, V128 tb0, V128 tb1, V128 tb2)
|
public static V128 Tbx2(V128 dest, V128 vector, int bytes, V128 tb0, V128 tb1)
|
||||||
{
|
{
|
||||||
return Tbl(vector, 16, tb0, tb1, tb2);
|
return TblOrTbx(dest, vector, bytes, tb0, tb1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static V128 Tbl4_V64(V128 vector, V128 tb0, V128 tb1, V128 tb2, V128 tb3)
|
public static V128 Tbx3(V128 dest, V128 vector, int bytes, V128 tb0, V128 tb1, V128 tb2)
|
||||||
{
|
{
|
||||||
return Tbl(vector, 8, tb0, tb1, tb2, tb3);
|
return TblOrTbx(dest, vector, bytes, tb0, tb1, tb2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static V128 Tbl4_V128(V128 vector, V128 tb0, V128 tb1, V128 tb2, V128 tb3)
|
public static V128 Tbx4(V128 dest, V128 vector, int bytes, V128 tb0, V128 tb1, V128 tb2, V128 tb3)
|
||||||
{
|
{
|
||||||
return Tbl(vector, 16, tb0, tb1, tb2, tb3);
|
return TblOrTbx(dest, vector, bytes, tb0, tb1, tb2, tb3);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static V128 Tbl(V128 vector, int bytes, params V128[] tb)
|
private static V128 TblOrTbx(V128 dest, V128 vector, int bytes, params V128[] tb)
|
||||||
{
|
{
|
||||||
byte[] res = new byte[16];
|
byte[] res = new byte[16];
|
||||||
|
|
||||||
|
if (dest != default)
|
||||||
|
{
|
||||||
|
Buffer.BlockCopy(dest.ToArray(), 0, res, 0, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
byte[] table = new byte[tb.Length * 16];
|
byte[] table = new byte[tb.Length * 16];
|
||||||
|
|
||||||
for (byte index = 0; index < tb.Length; index++)
|
for (byte index = 0; index < tb.Length; index++)
|
||||||
|
|
|
@ -99,14 +99,14 @@ namespace Ryujinx.Tests.Cpu
|
||||||
ulong x2 = 0,
|
ulong x2 = 0,
|
||||||
ulong x3 = 0,
|
ulong x3 = 0,
|
||||||
ulong x31 = 0,
|
ulong x31 = 0,
|
||||||
V128 v0 = default(V128),
|
V128 v0 = default,
|
||||||
V128 v1 = default(V128),
|
V128 v1 = default,
|
||||||
V128 v2 = default(V128),
|
V128 v2 = default,
|
||||||
V128 v3 = default(V128),
|
V128 v3 = default,
|
||||||
V128 v4 = default(V128),
|
V128 v4 = default,
|
||||||
V128 v5 = default(V128),
|
V128 v5 = default,
|
||||||
V128 v30 = default(V128),
|
V128 v30 = default,
|
||||||
V128 v31 = default(V128),
|
V128 v31 = default,
|
||||||
bool overflow = false,
|
bool overflow = false,
|
||||||
bool carry = false,
|
bool carry = false,
|
||||||
bool zero = false,
|
bool zero = false,
|
||||||
|
@ -182,14 +182,14 @@ namespace Ryujinx.Tests.Cpu
|
||||||
ulong x2 = 0,
|
ulong x2 = 0,
|
||||||
ulong x3 = 0,
|
ulong x3 = 0,
|
||||||
ulong x31 = 0,
|
ulong x31 = 0,
|
||||||
V128 v0 = default(V128),
|
V128 v0 = default,
|
||||||
V128 v1 = default(V128),
|
V128 v1 = default,
|
||||||
V128 v2 = default(V128),
|
V128 v2 = default,
|
||||||
V128 v3 = default(V128),
|
V128 v3 = default,
|
||||||
V128 v4 = default(V128),
|
V128 v4 = default,
|
||||||
V128 v5 = default(V128),
|
V128 v5 = default,
|
||||||
V128 v30 = default(V128),
|
V128 v30 = default,
|
||||||
V128 v31 = default(V128),
|
V128 v31 = default,
|
||||||
bool overflow = false,
|
bool overflow = false,
|
||||||
bool carry = false,
|
bool carry = false,
|
||||||
bool zero = false,
|
bool zero = false,
|
||||||
|
|
|
@ -98,55 +98,60 @@ namespace Ryujinx.Tests.Cpu
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region "ValueSource (Opcodes)"
|
#region "ValueSource (Opcodes)"
|
||||||
private static uint[] _SingleRegTbl_V_8B_16B_()
|
private static uint[] _SingleRegisterTable_V_8B_16B_()
|
||||||
{
|
{
|
||||||
return new uint[]
|
return new uint[]
|
||||||
{
|
{
|
||||||
0x0E000000u, // TBL V0.8B, { V0.16B }, V0.8B
|
0x0E000000u, // TBL V0.8B, { V0.16B }, V0.8B
|
||||||
|
0x0E001000u // TBX V0.8B, { V0.16B }, V0.8B
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static uint[] _TwoRegTbl_V_8B_16B_()
|
private static uint[] _TwoRegisterTable_V_8B_16B_()
|
||||||
{
|
{
|
||||||
return new uint[]
|
return new uint[]
|
||||||
{
|
{
|
||||||
0x0E002000u, // TBL V0.8B, { V0.16B, V1.16B }, V0.8B
|
0x0E002000u, // TBL V0.8B, { V0.16B, V1.16B }, V0.8B
|
||||||
|
0x0E003000u // TBX V0.8B, { V0.16B, V1.16B }, V0.8B
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static uint[] _ThreeRegTbl_V_8B_16B_()
|
private static uint[] _ThreeRegisterTable_V_8B_16B_()
|
||||||
{
|
{
|
||||||
return new uint[]
|
return new uint[]
|
||||||
{
|
{
|
||||||
0x0E004000u, // TBL V0.8B, { V0.16B, V1.16B, V2.16B }, V0.8B
|
0x0E004000u, // TBL V0.8B, { V0.16B, V1.16B, V2.16B }, V0.8B
|
||||||
|
0x0E005000u // TBX V0.8B, { V0.16B, V1.16B, V2.16B }, V0.8B
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static uint[] _FourRegTbl_V_8B_16B_()
|
private static uint[] _FourRegisterTable_V_8B_16B_()
|
||||||
{
|
{
|
||||||
return new uint[]
|
return new uint[]
|
||||||
{
|
{
|
||||||
0x0E006000u, // TBL V0.8B, { V0.16B, V1.16B, V2.16B, V3.16B }, V0.8B
|
0x0E006000u, // TBL V0.8B, { V0.16B, V1.16B, V2.16B, V3.16B }, V0.8B
|
||||||
|
0x0E006000u // TBX V0.8B, { V0.16B, V1.16B, V2.16B, V3.16B }, V0.8B
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
private const int RndCntDest = 2;
|
||||||
private const int RndCntTbls = 2;
|
private const int RndCntTbls = 2;
|
||||||
private const int RndCntIdxs = 2;
|
private const int RndCntIdxs = 2;
|
||||||
|
|
||||||
[Test, Pairwise, Description("TBL <Vd>.<Ta>, { <Vn>.16B }, <Vm>.<Ta>")]
|
[Test, Pairwise]
|
||||||
public void SingleRegTbl_V_8B_16B([ValueSource("_SingleRegTbl_V_8B_16B_")] uint opcodes,
|
public void SingleRegisterTable_V_8B_16B([ValueSource("_SingleRegisterTable_V_8B_16B_")] uint opcodes,
|
||||||
[Values(0u)] uint rd,
|
[Values(0u)] uint rd,
|
||||||
[Values(1u)] uint rn,
|
[Values(1u)] uint rn,
|
||||||
[Values(2u)] uint rm,
|
[Values(2u)] uint rm,
|
||||||
[ValueSource("_8B_")] [Random(RndCntTbls)] ulong table0,
|
[ValueSource("_8B_")] [Random(RndCntDest)] ulong z,
|
||||||
[ValueSource("_GenIdxsForTbl1_")] ulong indexes,
|
[ValueSource("_8B_")] [Random(RndCntTbls)] ulong table0,
|
||||||
[Values(0b0u, 0b1u)] uint q) // <8B, 16B>
|
[ValueSource("_GenIdxsForTbl1_")] ulong indexes,
|
||||||
|
[Values(0b0u, 0b1u)] uint q) // <8B, 16B>
|
||||||
{
|
{
|
||||||
opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0);
|
opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0);
|
||||||
opcodes |= ((q & 1) << 30);
|
opcodes |= ((q & 1) << 30);
|
||||||
|
|
||||||
ulong z = TestContext.CurrentContext.Random.NextULong();
|
|
||||||
V128 v0 = MakeVectorE0E1(z, z);
|
V128 v0 = MakeVectorE0E1(z, z);
|
||||||
V128 v1 = MakeVectorE0E1(table0, table0);
|
V128 v1 = MakeVectorE0E1(table0, table0);
|
||||||
V128 v2 = MakeVectorE0E1(indexes, q == 1u ? indexes : 0ul);
|
V128 v2 = MakeVectorE0E1(indexes, q == 1u ? indexes : 0ul);
|
||||||
|
@ -156,20 +161,20 @@ namespace Ryujinx.Tests.Cpu
|
||||||
CompareAgainstUnicorn();
|
CompareAgainstUnicorn();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test, Pairwise, Description("TBL <Vd>.<Ta>, { <Vn>.16B, <Vn+1>.16B }, <Vm>.<Ta>")]
|
[Test, Pairwise]
|
||||||
public void TwoRegTbl_V_8B_16B([ValueSource("_TwoRegTbl_V_8B_16B_")] uint opcodes,
|
public void TwoRegisterTable_V_8B_16B([ValueSource("_TwoRegisterTable_V_8B_16B_")] uint opcodes,
|
||||||
[Values(0u)] uint rd,
|
[Values(0u)] uint rd,
|
||||||
[Values(1u)] uint rn,
|
[Values(1u)] uint rn,
|
||||||
[Values(3u)] uint rm,
|
[Values(3u)] uint rm,
|
||||||
[ValueSource("_8B_")] [Random(RndCntTbls)] ulong table0,
|
[ValueSource("_8B_")] [Random(RndCntDest)] ulong z,
|
||||||
[ValueSource("_8B_")] [Random(RndCntTbls)] ulong table1,
|
[ValueSource("_8B_")] [Random(RndCntTbls)] ulong table0,
|
||||||
[ValueSource("_GenIdxsForTbl2_")] ulong indexes,
|
[ValueSource("_8B_")] [Random(RndCntTbls)] ulong table1,
|
||||||
[Values(0b0u, 0b1u)] uint q) // <8B, 16B>
|
[ValueSource("_GenIdxsForTbl2_")] ulong indexes,
|
||||||
|
[Values(0b0u, 0b1u)] uint q) // <8B, 16B>
|
||||||
{
|
{
|
||||||
opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0);
|
opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0);
|
||||||
opcodes |= ((q & 1) << 30);
|
opcodes |= ((q & 1) << 30);
|
||||||
|
|
||||||
ulong z = TestContext.CurrentContext.Random.NextULong();
|
|
||||||
V128 v0 = MakeVectorE0E1(z, z);
|
V128 v0 = MakeVectorE0E1(z, z);
|
||||||
V128 v1 = MakeVectorE0E1(table0, table0);
|
V128 v1 = MakeVectorE0E1(table0, table0);
|
||||||
V128 v2 = MakeVectorE0E1(table1, table1);
|
V128 v2 = MakeVectorE0E1(table1, table1);
|
||||||
|
@ -180,20 +185,20 @@ namespace Ryujinx.Tests.Cpu
|
||||||
CompareAgainstUnicorn();
|
CompareAgainstUnicorn();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test, Pairwise, Description("TBL <Vd>.<Ta>, { <Vn>.16B, <Vn+1>.16B }, <Vm>.<Ta>")]
|
[Test, Pairwise]
|
||||||
public void Mod_TwoRegTbl_V_8B_16B([ValueSource("_TwoRegTbl_V_8B_16B_")] uint opcodes,
|
public void Mod_TwoRegisterTable_V_8B_16B([ValueSource("_TwoRegisterTable_V_8B_16B_")] uint opcodes,
|
||||||
[Values(30u, 1u)] uint rd,
|
[Values(30u, 1u)] uint rd,
|
||||||
[Values(31u)] uint rn,
|
[Values(31u)] uint rn,
|
||||||
[Values(1u, 30u)] uint rm,
|
[Values(1u, 30u)] uint rm,
|
||||||
[ValueSource("_8B_")] [Random(RndCntTbls)] ulong table0,
|
[ValueSource("_8B_")] [Random(RndCntDest)] ulong z,
|
||||||
[ValueSource("_8B_")] [Random(RndCntTbls)] ulong table1,
|
[ValueSource("_8B_")] [Random(RndCntTbls)] ulong table0,
|
||||||
[ValueSource("_GenIdxsForTbl2_")] ulong indexes,
|
[ValueSource("_8B_")] [Random(RndCntTbls)] ulong table1,
|
||||||
[Values(0b0u, 0b1u)] uint q) // <8B, 16B>
|
[ValueSource("_GenIdxsForTbl2_")] ulong indexes,
|
||||||
|
[Values(0b0u, 0b1u)] uint q) // <8B, 16B>
|
||||||
{
|
{
|
||||||
opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0);
|
opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0);
|
||||||
opcodes |= ((q & 1) << 30);
|
opcodes |= ((q & 1) << 30);
|
||||||
|
|
||||||
ulong z = TestContext.CurrentContext.Random.NextULong();
|
|
||||||
V128 v30 = MakeVectorE0E1(z, z);
|
V128 v30 = MakeVectorE0E1(z, z);
|
||||||
V128 v31 = MakeVectorE0E1(table0, table0);
|
V128 v31 = MakeVectorE0E1(table0, table0);
|
||||||
V128 v0 = MakeVectorE0E1(table1, table1);
|
V128 v0 = MakeVectorE0E1(table1, table1);
|
||||||
|
@ -204,21 +209,21 @@ namespace Ryujinx.Tests.Cpu
|
||||||
CompareAgainstUnicorn();
|
CompareAgainstUnicorn();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test, Pairwise, Description("TBL <Vd>.<Ta>, { <Vn>.16B, <Vn+1>.16B, <Vn+2>.16B }, <Vm>.<Ta>")]
|
[Test, Pairwise]
|
||||||
public void ThreeRegTbl_V_8B_16B([ValueSource("_ThreeRegTbl_V_8B_16B_")] uint opcodes,
|
public void ThreeRegisterTable_V_8B_16B([ValueSource("_ThreeRegisterTable_V_8B_16B_")] uint opcodes,
|
||||||
[Values(0u)] uint rd,
|
[Values(0u)] uint rd,
|
||||||
[Values(1u)] uint rn,
|
[Values(1u)] uint rn,
|
||||||
[Values(4u)] uint rm,
|
[Values(4u)] uint rm,
|
||||||
[ValueSource("_8B_")] [Random(RndCntTbls)] ulong table0,
|
[ValueSource("_8B_")] [Random(RndCntDest)] ulong z,
|
||||||
[ValueSource("_8B_")] [Random(RndCntTbls)] ulong table1,
|
[ValueSource("_8B_")] [Random(RndCntTbls)] ulong table0,
|
||||||
[ValueSource("_8B_")] [Random(RndCntTbls)] ulong table2,
|
[ValueSource("_8B_")] [Random(RndCntTbls)] ulong table1,
|
||||||
[ValueSource("_GenIdxsForTbl3_")] ulong indexes,
|
[ValueSource("_8B_")] [Random(RndCntTbls)] ulong table2,
|
||||||
[Values(0b0u, 0b1u)] uint q) // <8B, 16B>
|
[ValueSource("_GenIdxsForTbl3_")] ulong indexes,
|
||||||
|
[Values(0b0u, 0b1u)] uint q) // <8B, 16B>
|
||||||
{
|
{
|
||||||
opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0);
|
opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0);
|
||||||
opcodes |= ((q & 1) << 30);
|
opcodes |= ((q & 1) << 30);
|
||||||
|
|
||||||
ulong z = TestContext.CurrentContext.Random.NextULong();
|
|
||||||
V128 v0 = MakeVectorE0E1(z, z);
|
V128 v0 = MakeVectorE0E1(z, z);
|
||||||
V128 v1 = MakeVectorE0E1(table0, table0);
|
V128 v1 = MakeVectorE0E1(table0, table0);
|
||||||
V128 v2 = MakeVectorE0E1(table1, table1);
|
V128 v2 = MakeVectorE0E1(table1, table1);
|
||||||
|
@ -230,21 +235,21 @@ namespace Ryujinx.Tests.Cpu
|
||||||
CompareAgainstUnicorn();
|
CompareAgainstUnicorn();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test, Pairwise, Description("TBL <Vd>.<Ta>, { <Vn>.16B, <Vn+1>.16B, <Vn+2>.16B }, <Vm>.<Ta>")]
|
[Test, Pairwise]
|
||||||
public void Mod_ThreeRegTbl_V_8B_16B([ValueSource("_ThreeRegTbl_V_8B_16B_")] uint opcodes,
|
public void Mod_ThreeRegisterTable_V_8B_16B([ValueSource("_ThreeRegisterTable_V_8B_16B_")] uint opcodes,
|
||||||
[Values(30u, 2u)] uint rd,
|
[Values(30u, 2u)] uint rd,
|
||||||
[Values(31u)] uint rn,
|
[Values(31u)] uint rn,
|
||||||
[Values(2u, 30u)] uint rm,
|
[Values(2u, 30u)] uint rm,
|
||||||
[ValueSource("_8B_")] [Random(RndCntTbls)] ulong table0,
|
[ValueSource("_8B_")] [Random(RndCntDest)] ulong z,
|
||||||
[ValueSource("_8B_")] [Random(RndCntTbls)] ulong table1,
|
[ValueSource("_8B_")] [Random(RndCntTbls)] ulong table0,
|
||||||
[ValueSource("_8B_")] [Random(RndCntTbls)] ulong table2,
|
[ValueSource("_8B_")] [Random(RndCntTbls)] ulong table1,
|
||||||
[ValueSource("_GenIdxsForTbl3_")] ulong indexes,
|
[ValueSource("_8B_")] [Random(RndCntTbls)] ulong table2,
|
||||||
[Values(0b0u, 0b1u)] uint q) // <8B, 16B>
|
[ValueSource("_GenIdxsForTbl3_")] ulong indexes,
|
||||||
|
[Values(0b0u, 0b1u)] uint q) // <8B, 16B>
|
||||||
{
|
{
|
||||||
opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0);
|
opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0);
|
||||||
opcodes |= ((q & 1) << 30);
|
opcodes |= ((q & 1) << 30);
|
||||||
|
|
||||||
ulong z = TestContext.CurrentContext.Random.NextULong();
|
|
||||||
V128 v30 = MakeVectorE0E1(z, z);
|
V128 v30 = MakeVectorE0E1(z, z);
|
||||||
V128 v31 = MakeVectorE0E1(table0, table0);
|
V128 v31 = MakeVectorE0E1(table0, table0);
|
||||||
V128 v0 = MakeVectorE0E1(table1, table1);
|
V128 v0 = MakeVectorE0E1(table1, table1);
|
||||||
|
@ -256,22 +261,22 @@ namespace Ryujinx.Tests.Cpu
|
||||||
CompareAgainstUnicorn();
|
CompareAgainstUnicorn();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test, Pairwise, Description("TBL <Vd>.<Ta>, { <Vn>.16B, <Vn+1>.16B, <Vn+2>.16B, <Vn+3>.16B }, <Vm>.<Ta>")]
|
[Test, Pairwise]
|
||||||
public void FourRegTbl_V_8B_16B([ValueSource("_FourRegTbl_V_8B_16B_")] uint opcodes,
|
public void FourRegisterTable_V_8B_16B([ValueSource("_FourRegisterTable_V_8B_16B_")] uint opcodes,
|
||||||
[Values(0u)] uint rd,
|
[Values(0u)] uint rd,
|
||||||
[Values(1u)] uint rn,
|
[Values(1u)] uint rn,
|
||||||
[Values(5u)] uint rm,
|
[Values(5u)] uint rm,
|
||||||
[ValueSource("_8B_")] [Random(RndCntTbls)] ulong table0,
|
[ValueSource("_8B_")] [Random(RndCntDest)] ulong z,
|
||||||
[ValueSource("_8B_")] [Random(RndCntTbls)] ulong table1,
|
[ValueSource("_8B_")] [Random(RndCntTbls)] ulong table0,
|
||||||
[ValueSource("_8B_")] [Random(RndCntTbls)] ulong table2,
|
[ValueSource("_8B_")] [Random(RndCntTbls)] ulong table1,
|
||||||
[ValueSource("_8B_")] [Random(RndCntTbls)] ulong table3,
|
[ValueSource("_8B_")] [Random(RndCntTbls)] ulong table2,
|
||||||
[ValueSource("_GenIdxsForTbl4_")] ulong indexes,
|
[ValueSource("_8B_")] [Random(RndCntTbls)] ulong table3,
|
||||||
[Values(0b0u, 0b1u)] uint q) // <8B, 16B>
|
[ValueSource("_GenIdxsForTbl4_")] ulong indexes,
|
||||||
|
[Values(0b0u, 0b1u)] uint q) // <8B, 16B>
|
||||||
{
|
{
|
||||||
opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0);
|
opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0);
|
||||||
opcodes |= ((q & 1) << 30);
|
opcodes |= ((q & 1) << 30);
|
||||||
|
|
||||||
ulong z = TestContext.CurrentContext.Random.NextULong();
|
|
||||||
V128 v0 = MakeVectorE0E1(z, z);
|
V128 v0 = MakeVectorE0E1(z, z);
|
||||||
V128 v1 = MakeVectorE0E1(table0, table0);
|
V128 v1 = MakeVectorE0E1(table0, table0);
|
||||||
V128 v2 = MakeVectorE0E1(table1, table1);
|
V128 v2 = MakeVectorE0E1(table1, table1);
|
||||||
|
@ -284,22 +289,22 @@ namespace Ryujinx.Tests.Cpu
|
||||||
CompareAgainstUnicorn();
|
CompareAgainstUnicorn();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test, Pairwise, Description("TBL <Vd>.<Ta>, { <Vn>.16B, <Vn+1>.16B, <Vn+2>.16B, <Vn+3>.16B }, <Vm>.<Ta>")]
|
[Test, Pairwise]
|
||||||
public void Mod_FourRegTbl_V_8B_16B([ValueSource("_FourRegTbl_V_8B_16B_")] uint opcodes,
|
public void Mod_FourRegisterTable_V_8B_16B([ValueSource("_FourRegisterTable_V_8B_16B_")] uint opcodes,
|
||||||
[Values(30u, 3u)] uint rd,
|
[Values(30u, 3u)] uint rd,
|
||||||
[Values(31u)] uint rn,
|
[Values(31u)] uint rn,
|
||||||
[Values(3u, 30u)] uint rm,
|
[Values(3u, 30u)] uint rm,
|
||||||
[ValueSource("_8B_")] [Random(RndCntTbls)] ulong table0,
|
[ValueSource("_8B_")] [Random(RndCntDest)] ulong z,
|
||||||
[ValueSource("_8B_")] [Random(RndCntTbls)] ulong table1,
|
[ValueSource("_8B_")] [Random(RndCntTbls)] ulong table0,
|
||||||
[ValueSource("_8B_")] [Random(RndCntTbls)] ulong table2,
|
[ValueSource("_8B_")] [Random(RndCntTbls)] ulong table1,
|
||||||
[ValueSource("_8B_")] [Random(RndCntTbls)] ulong table3,
|
[ValueSource("_8B_")] [Random(RndCntTbls)] ulong table2,
|
||||||
[ValueSource("_GenIdxsForTbl4_")] ulong indexes,
|
[ValueSource("_8B_")] [Random(RndCntTbls)] ulong table3,
|
||||||
[Values(0b0u, 0b1u)] uint q) // <8B, 16B>
|
[ValueSource("_GenIdxsForTbl4_")] ulong indexes,
|
||||||
|
[Values(0b0u, 0b1u)] uint q) // <8B, 16B>
|
||||||
{
|
{
|
||||||
opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0);
|
opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0);
|
||||||
opcodes |= ((q & 1) << 30);
|
opcodes |= ((q & 1) << 30);
|
||||||
|
|
||||||
ulong z = TestContext.CurrentContext.Random.NextULong();
|
|
||||||
V128 v30 = MakeVectorE0E1(z, z);
|
V128 v30 = MakeVectorE0E1(z, z);
|
||||||
V128 v31 = MakeVectorE0E1(table0, table0);
|
V128 v31 = MakeVectorE0E1(table0, table0);
|
||||||
V128 v0 = MakeVectorE0E1(table1, table1);
|
V128 v0 = MakeVectorE0E1(table1, table1);
|
||||||
|
|
|
@ -27,9 +27,9 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.3.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="3.13.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
Loading…
Reference in a new issue