|
@@ -26,6 +26,23 @@ namespace Ryujinx.Tests.Cpu
|
|
|
0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul };
|
|
0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ private static ulong[] _1H1S1D_()
|
|
|
|
|
+ {
|
|
|
|
|
+ return new ulong[] { 0x0000000000000000ul, 0x0000000000007FFFul,
|
|
|
|
|
+ 0x0000000000008000ul, 0x000000000000FFFFul,
|
|
|
|
|
+ 0x000000007FFFFFFFul, 0x0000000080000000ul,
|
|
|
|
|
+ 0x00000000FFFFFFFFul, 0x7FFFFFFFFFFFFFFFul,
|
|
|
|
|
+ 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static ulong[] _4H2S1D_()
|
|
|
|
|
+ {
|
|
|
|
|
+ return new ulong[] { 0x0000000000000000ul, 0x7FFF7FFF7FFF7FFFul,
|
|
|
|
|
+ 0x8000800080008000ul, 0x7FFFFFFF7FFFFFFFul,
|
|
|
|
|
+ 0x8000000080000000ul, 0x7FFFFFFFFFFFFFFFul,
|
|
|
|
|
+ 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
private static ulong[] _8B4H_()
|
|
private static ulong[] _8B4H_()
|
|
|
{
|
|
{
|
|
|
return new ulong[] { 0x0000000000000000ul, 0x7F7F7F7F7F7F7F7Ful,
|
|
return new ulong[] { 0x0000000000000000ul, 0x7F7F7F7F7F7F7F7Ful,
|
|
@@ -64,8 +81,11 @@ namespace Ryujinx.Tests.Cpu
|
|
|
AArch64.V(1, new Bits(A));
|
|
AArch64.V(1, new Bits(A));
|
|
|
SimdFp.Abs_S(Op[23, 22], Op[9, 5], Op[4, 0]);
|
|
SimdFp.Abs_S(Op[23, 22], Op[9, 5], Op[4, 0]);
|
|
|
|
|
|
|
|
- Assert.That(ThreadState.V0.X0, Is.EqualTo(AArch64.V(64, 0).ToUInt64()));
|
|
|
|
|
- Assert.That(ThreadState.V0.X1, Is.Zero);
|
|
|
|
|
|
|
+ Assert.Multiple(() =>
|
|
|
|
|
+ {
|
|
|
|
|
+ Assert.That(ThreadState.V0.X0, Is.EqualTo(AArch64.V(64, 0).ToUInt64()));
|
|
|
|
|
+ Assert.That(ThreadState.V0.X1, Is.Zero);
|
|
|
|
|
+ });
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
[Test, Description("ABS <Vd>.<T>, <Vn>.<T>")]
|
|
[Test, Description("ABS <Vd>.<T>, <Vn>.<T>")]
|
|
@@ -83,8 +103,11 @@ namespace Ryujinx.Tests.Cpu
|
|
|
AArch64.V(1, new Bits(A));
|
|
AArch64.V(1, new Bits(A));
|
|
|
SimdFp.Abs_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]);
|
|
SimdFp.Abs_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]);
|
|
|
|
|
|
|
|
- Assert.That(ThreadState.V0.X0, Is.EqualTo(AArch64.V(64, 0).ToUInt64()));
|
|
|
|
|
- Assert.That(ThreadState.V0.X1, Is.Zero);
|
|
|
|
|
|
|
+ Assert.Multiple(() =>
|
|
|
|
|
+ {
|
|
|
|
|
+ Assert.That(ThreadState.V0.X0, Is.EqualTo(AArch64.V(64, 0).ToUInt64()));
|
|
|
|
|
+ Assert.That(ThreadState.V0.X1, Is.Zero);
|
|
|
|
|
+ });
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
[Test, Pairwise, Description("ABS <Vd>.<T>, <Vn>.<T>")]
|
|
[Test, Pairwise, Description("ABS <Vd>.<T>, <Vn>.<T>")]
|
|
@@ -149,8 +172,11 @@ namespace Ryujinx.Tests.Cpu
|
|
|
AArch64.V(1, new Bits(A));
|
|
AArch64.V(1, new Bits(A));
|
|
|
SimdFp.Addv_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]);
|
|
SimdFp.Addv_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]);
|
|
|
|
|
|
|
|
- Assert.That(ThreadState.V0.X0, Is.EqualTo(AArch64.V(64, 0).ToUInt64()));
|
|
|
|
|
- Assert.That(ThreadState.V0.X1, Is.Zero);
|
|
|
|
|
|
|
+ Assert.Multiple(() =>
|
|
|
|
|
+ {
|
|
|
|
|
+ Assert.That(ThreadState.V0.X0, Is.EqualTo(AArch64.V(64, 0).ToUInt64()));
|
|
|
|
|
+ Assert.That(ThreadState.V0.X1, Is.Zero);
|
|
|
|
|
+ });
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
[Test, Pairwise, Description("ADDV <V><d>, <Vn>.<T>")]
|
|
[Test, Pairwise, Description("ADDV <V><d>, <Vn>.<T>")]
|
|
@@ -194,8 +220,11 @@ namespace Ryujinx.Tests.Cpu
|
|
|
AArch64.V(1, new Bits(A));
|
|
AArch64.V(1, new Bits(A));
|
|
|
SimdFp.Cls_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]);
|
|
SimdFp.Cls_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]);
|
|
|
|
|
|
|
|
- Assert.That(ThreadState.V0.X0, Is.EqualTo(AArch64.V(64, 0).ToUInt64()));
|
|
|
|
|
- Assert.That(ThreadState.V0.X1, Is.Zero);
|
|
|
|
|
|
|
+ Assert.Multiple(() =>
|
|
|
|
|
+ {
|
|
|
|
|
+ Assert.That(ThreadState.V0.X0, Is.EqualTo(AArch64.V(64, 0).ToUInt64()));
|
|
|
|
|
+ Assert.That(ThreadState.V0.X1, Is.Zero);
|
|
|
|
|
+ });
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
[Test, Pairwise, Description("CLS <Vd>.<T>, <Vn>.<T>")]
|
|
[Test, Pairwise, Description("CLS <Vd>.<T>, <Vn>.<T>")]
|
|
@@ -236,8 +265,11 @@ namespace Ryujinx.Tests.Cpu
|
|
|
AArch64.V(1, new Bits(A));
|
|
AArch64.V(1, new Bits(A));
|
|
|
SimdFp.Clz_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]);
|
|
SimdFp.Clz_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]);
|
|
|
|
|
|
|
|
- Assert.That(ThreadState.V0.X0, Is.EqualTo(AArch64.V(64, 0).ToUInt64()));
|
|
|
|
|
- Assert.That(ThreadState.V0.X1, Is.Zero);
|
|
|
|
|
|
|
+ Assert.Multiple(() =>
|
|
|
|
|
+ {
|
|
|
|
|
+ Assert.That(ThreadState.V0.X0, Is.EqualTo(AArch64.V(64, 0).ToUInt64()));
|
|
|
|
|
+ Assert.That(ThreadState.V0.X1, Is.Zero);
|
|
|
|
|
+ });
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
[Test, Pairwise, Description("CLZ <Vd>.<T>, <Vn>.<T>")]
|
|
[Test, Pairwise, Description("CLZ <Vd>.<T>, <Vn>.<T>")]
|
|
@@ -276,8 +308,11 @@ namespace Ryujinx.Tests.Cpu
|
|
|
AArch64.V(1, new Bits(A));
|
|
AArch64.V(1, new Bits(A));
|
|
|
SimdFp.Neg_S(Op[23, 22], Op[9, 5], Op[4, 0]);
|
|
SimdFp.Neg_S(Op[23, 22], Op[9, 5], Op[4, 0]);
|
|
|
|
|
|
|
|
- Assert.That(ThreadState.V0.X0, Is.EqualTo(AArch64.V(64, 0).ToUInt64()));
|
|
|
|
|
- Assert.That(ThreadState.V0.X1, Is.Zero);
|
|
|
|
|
|
|
+ Assert.Multiple(() =>
|
|
|
|
|
+ {
|
|
|
|
|
+ Assert.That(ThreadState.V0.X0, Is.EqualTo(AArch64.V(64, 0).ToUInt64()));
|
|
|
|
|
+ Assert.That(ThreadState.V0.X1, Is.Zero);
|
|
|
|
|
+ });
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
[Test, Description("NEG <Vd>.<T>, <Vn>.<T>")]
|
|
[Test, Description("NEG <Vd>.<T>, <Vn>.<T>")]
|
|
@@ -295,8 +330,11 @@ namespace Ryujinx.Tests.Cpu
|
|
|
AArch64.V(1, new Bits(A));
|
|
AArch64.V(1, new Bits(A));
|
|
|
SimdFp.Neg_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]);
|
|
SimdFp.Neg_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]);
|
|
|
|
|
|
|
|
- Assert.That(ThreadState.V0.X0, Is.EqualTo(AArch64.V(64, 0).ToUInt64()));
|
|
|
|
|
- Assert.That(ThreadState.V0.X1, Is.Zero);
|
|
|
|
|
|
|
+ Assert.Multiple(() =>
|
|
|
|
|
+ {
|
|
|
|
|
+ Assert.That(ThreadState.V0.X0, Is.EqualTo(AArch64.V(64, 0).ToUInt64()));
|
|
|
|
|
+ Assert.That(ThreadState.V0.X1, Is.Zero);
|
|
|
|
|
+ });
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
[Test, Pairwise, Description("NEG <Vd>.<T>, <Vn>.<T>")]
|
|
[Test, Pairwise, Description("NEG <Vd>.<T>, <Vn>.<T>")]
|
|
@@ -321,6 +359,158 @@ namespace Ryujinx.Tests.Cpu
|
|
|
Assert.That(ThreadState.V0.X1, Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64()));
|
|
Assert.That(ThreadState.V0.X1, Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64()));
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ [Test, Description("SQXTN <Vb><d>, <Va><n>")]
|
|
|
|
|
+ public void Sqxtn_S_HB_SH_DS([ValueSource("_1H1S1D_")] [Random(1)] ulong A,
|
|
|
|
|
+ [Values(0b00u, 0b01u, 0b10u)] uint size) // <HB, SH, DS>
|
|
|
|
|
+ {
|
|
|
|
|
+ uint Opcode = 0x5E214820; // SQXTN B0, H1
|
|
|
|
|
+ Opcode |= ((size & 3) << 22);
|
|
|
|
|
+ Bits Op = new Bits(Opcode);
|
|
|
|
|
+
|
|
|
|
|
+ AVec V0 = new AVec { X0 = TestContext.CurrentContext.Random.NextULong(),
|
|
|
|
|
+ X1 = TestContext.CurrentContext.Random.NextULong() };
|
|
|
|
|
+ AVec V1 = new AVec { X0 = A };
|
|
|
|
|
+ AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1);
|
|
|
|
|
+
|
|
|
|
|
+ AArch64.Vpart(0, 0, new Bits(TestContext.CurrentContext.Random.NextULong()));
|
|
|
|
|
+ AArch64.V(1, new Bits(A));
|
|
|
|
|
+ SimdFp.Sqxtn_S(Op[23, 22], Op[9, 5], Op[4, 0]);
|
|
|
|
|
+
|
|
|
|
|
+ Assert.Multiple(() =>
|
|
|
|
|
+ {
|
|
|
|
|
+ Assert.That(ThreadState.V0.X0, Is.EqualTo(AArch64.V(64, 0).ToUInt64()));
|
|
|
|
|
+ Assert.That(ThreadState.V0.X1, Is.Zero);
|
|
|
|
|
+ });
|
|
|
|
|
+ Assert.That(((ThreadState.Fpsr >> 27) & 1) != 0, Is.EqualTo(Shared.FPSR[27])); // FIXME: Temporary solution.
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ [Test, Pairwise, Description("SQXTN{2} <Vd>.<Tb>, <Vn>.<Ta>")]
|
|
|
|
|
+ public void Sqxtn_V_8H8B_4S4H_2D2S([ValueSource("_4H2S1D_")] [Random(1)] ulong A0,
|
|
|
|
|
+ [ValueSource("_4H2S1D_")] [Random(1)] ulong A1,
|
|
|
|
|
+ [Values(0b00u, 0b01u, 0b10u)] uint size) // <8H8B, 4S4H, 2D2S>
|
|
|
|
|
+ {
|
|
|
|
|
+ uint Opcode = 0x0E214820; // SQXTN V0.8B, V1.8H
|
|
|
|
|
+ Opcode |= ((size & 3) << 22);
|
|
|
|
|
+ Bits Op = new Bits(Opcode);
|
|
|
|
|
+
|
|
|
|
|
+ AVec V0 = new AVec { X1 = TestContext.CurrentContext.Random.NextULong() };
|
|
|
|
|
+ AVec V1 = new AVec { X0 = A0, X1 = A1 };
|
|
|
|
|
+ AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1);
|
|
|
|
|
+
|
|
|
|
|
+ AArch64.Vpart(1, 0, new Bits(A0));
|
|
|
|
|
+ AArch64.Vpart(1, 1, new Bits(A1));
|
|
|
|
|
+ SimdFp.Sqxtn_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]);
|
|
|
|
|
+
|
|
|
|
|
+ Assert.Multiple(() =>
|
|
|
|
|
+ {
|
|
|
|
|
+ Assert.That(ThreadState.V0.X0, Is.EqualTo(AArch64.V(64, 0).ToUInt64()));
|
|
|
|
|
+ Assert.That(ThreadState.V0.X1, Is.Zero);
|
|
|
|
|
+ });
|
|
|
|
|
+ Assert.That(((ThreadState.Fpsr >> 27) & 1) != 0, Is.EqualTo(Shared.FPSR[27])); // FIXME: Temporary solution.
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ [Test, Pairwise, Description("SQXTN{2} <Vd>.<Tb>, <Vn>.<Ta>")]
|
|
|
|
|
+ public void Sqxtn_V_8H16B_4S8H_2D4S([ValueSource("_4H2S1D_")] [Random(1)] ulong A0,
|
|
|
|
|
+ [ValueSource("_4H2S1D_")] [Random(1)] ulong A1,
|
|
|
|
|
+ [Values(0b00u, 0b01u, 0b10u)] uint size) // <8H16B, 4S8H, 2D4S>
|
|
|
|
|
+ {
|
|
|
|
|
+ uint Opcode = 0x4E214820; // SQXTN2 V0.16B, V1.8H
|
|
|
|
|
+ Opcode |= ((size & 3) << 22);
|
|
|
|
|
+ Bits Op = new Bits(Opcode);
|
|
|
|
|
+
|
|
|
|
|
+ ulong _X0 = TestContext.CurrentContext.Random.NextULong();
|
|
|
|
|
+ AVec V0 = new AVec { X0 = _X0 };
|
|
|
|
|
+ AVec V1 = new AVec { X0 = A0, X1 = A1 };
|
|
|
|
|
+ AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1);
|
|
|
|
|
+
|
|
|
|
|
+ AArch64.Vpart(1, 0, new Bits(A0));
|
|
|
|
|
+ AArch64.Vpart(1, 1, new Bits(A1));
|
|
|
|
|
+ SimdFp.Sqxtn_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]);
|
|
|
|
|
+
|
|
|
|
|
+ Assert.Multiple(() =>
|
|
|
|
|
+ {
|
|
|
|
|
+ Assert.That(ThreadState.V0.X0, Is.EqualTo(_X0));
|
|
|
|
|
+ Assert.That(ThreadState.V0.X1, Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64()));
|
|
|
|
|
+ });
|
|
|
|
|
+ Assert.That(((ThreadState.Fpsr >> 27) & 1) != 0, Is.EqualTo(Shared.FPSR[27])); // FIXME: Temporary solution.
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ [Test, Description("UQXTN <Vb><d>, <Va><n>")]
|
|
|
|
|
+ public void Uqxtn_S_HB_SH_DS([ValueSource("_1H1S1D_")] [Random(1)] ulong A,
|
|
|
|
|
+ [Values(0b00u, 0b01u, 0b10u)] uint size) // <HB, SH, DS>
|
|
|
|
|
+ {
|
|
|
|
|
+ uint Opcode = 0x7E214820; // UQXTN B0, H1
|
|
|
|
|
+ Opcode |= ((size & 3) << 22);
|
|
|
|
|
+ Bits Op = new Bits(Opcode);
|
|
|
|
|
+
|
|
|
|
|
+ AVec V0 = new AVec { X0 = TestContext.CurrentContext.Random.NextULong(),
|
|
|
|
|
+ X1 = TestContext.CurrentContext.Random.NextULong() };
|
|
|
|
|
+ AVec V1 = new AVec { X0 = A };
|
|
|
|
|
+ AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1);
|
|
|
|
|
+
|
|
|
|
|
+ AArch64.Vpart(0, 0, new Bits(TestContext.CurrentContext.Random.NextULong()));
|
|
|
|
|
+ AArch64.V(1, new Bits(A));
|
|
|
|
|
+ SimdFp.Uqxtn_S(Op[23, 22], Op[9, 5], Op[4, 0]);
|
|
|
|
|
+
|
|
|
|
|
+ Assert.Multiple(() =>
|
|
|
|
|
+ {
|
|
|
|
|
+ Assert.That(ThreadState.V0.X0, Is.EqualTo(AArch64.V(64, 0).ToUInt64()));
|
|
|
|
|
+ Assert.That(ThreadState.V0.X1, Is.Zero);
|
|
|
|
|
+ });
|
|
|
|
|
+ Assert.That(((ThreadState.Fpsr >> 27) & 1) != 0, Is.EqualTo(Shared.FPSR[27])); // FIXME: Temporary solution.
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ [Test, Pairwise, Description("UQXTN{2} <Vd>.<Tb>, <Vn>.<Ta>")]
|
|
|
|
|
+ public void Uqxtn_V_8H8B_4S4H_2D2S([ValueSource("_4H2S1D_")] [Random(1)] ulong A0,
|
|
|
|
|
+ [ValueSource("_4H2S1D_")] [Random(1)] ulong A1,
|
|
|
|
|
+ [Values(0b00u, 0b01u, 0b10u)] uint size) // <8H8B, 4S4H, 2D2S>
|
|
|
|
|
+ {
|
|
|
|
|
+ uint Opcode = 0x2E214820; // UQXTN V0.8B, V1.8H
|
|
|
|
|
+ Opcode |= ((size & 3) << 22);
|
|
|
|
|
+ Bits Op = new Bits(Opcode);
|
|
|
|
|
+
|
|
|
|
|
+ AVec V0 = new AVec { X1 = TestContext.CurrentContext.Random.NextULong() };
|
|
|
|
|
+ AVec V1 = new AVec { X0 = A0, X1 = A1 };
|
|
|
|
|
+ AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1);
|
|
|
|
|
+
|
|
|
|
|
+ AArch64.Vpart(1, 0, new Bits(A0));
|
|
|
|
|
+ AArch64.Vpart(1, 1, new Bits(A1));
|
|
|
|
|
+ SimdFp.Uqxtn_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]);
|
|
|
|
|
+
|
|
|
|
|
+ Assert.Multiple(() =>
|
|
|
|
|
+ {
|
|
|
|
|
+ Assert.That(ThreadState.V0.X0, Is.EqualTo(AArch64.V(64, 0).ToUInt64()));
|
|
|
|
|
+ Assert.That(ThreadState.V0.X1, Is.Zero);
|
|
|
|
|
+ });
|
|
|
|
|
+ Assert.That(((ThreadState.Fpsr >> 27) & 1) != 0, Is.EqualTo(Shared.FPSR[27])); // FIXME: Temporary solution.
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ [Test, Pairwise, Description("UQXTN{2} <Vd>.<Tb>, <Vn>.<Ta>")]
|
|
|
|
|
+ public void Uqxtn_V_8H16B_4S8H_2D4S([ValueSource("_4H2S1D_")] [Random(1)] ulong A0,
|
|
|
|
|
+ [ValueSource("_4H2S1D_")] [Random(1)] ulong A1,
|
|
|
|
|
+ [Values(0b00u, 0b01u, 0b10u)] uint size) // <8H16B, 4S8H, 2D4S>
|
|
|
|
|
+ {
|
|
|
|
|
+ uint Opcode = 0x6E214820; // UQXTN2 V0.16B, V1.8H
|
|
|
|
|
+ Opcode |= ((size & 3) << 22);
|
|
|
|
|
+ Bits Op = new Bits(Opcode);
|
|
|
|
|
+
|
|
|
|
|
+ ulong _X0 = TestContext.CurrentContext.Random.NextULong();
|
|
|
|
|
+ AVec V0 = new AVec { X0 = _X0 };
|
|
|
|
|
+ AVec V1 = new AVec { X0 = A0, X1 = A1 };
|
|
|
|
|
+ AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1);
|
|
|
|
|
+
|
|
|
|
|
+ AArch64.Vpart(1, 0, new Bits(A0));
|
|
|
|
|
+ AArch64.Vpart(1, 1, new Bits(A1));
|
|
|
|
|
+ SimdFp.Uqxtn_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]);
|
|
|
|
|
+
|
|
|
|
|
+ Assert.Multiple(() =>
|
|
|
|
|
+ {
|
|
|
|
|
+ Assert.That(ThreadState.V0.X0, Is.EqualTo(_X0));
|
|
|
|
|
+ Assert.That(ThreadState.V0.X1, Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64()));
|
|
|
|
|
+ });
|
|
|
|
|
+ Assert.That(((ThreadState.Fpsr >> 27) & 1) != 0, Is.EqualTo(Shared.FPSR[27])); // FIXME: Temporary solution.
|
|
|
|
|
+ }
|
|
|
#endif
|
|
#endif
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|