diff --git a/ChocolArm64/AOpCodeTable.cs b/ChocolArm64/AOpCodeTable.cs index d32bd9cd2..4dc157549 100644 --- a/ChocolArm64/AOpCodeTable.cs +++ b/ChocolArm64/AOpCodeTable.cs @@ -8,7 +8,7 @@ namespace ChocolArm64 { static AOpCodeTable() { - #region "OpCode Table" +#region "OpCode Table" //Integer Set("x0011010000xxxxx000000xxxxxxxxxx", AInstEmit.Adc, typeof(AOpCodeAluRs)); Set("x0111010000xxxxx000000xxxxxxxxxx", AInstEmit.Adcs, typeof(AOpCodeAluRs)); @@ -41,6 +41,7 @@ namespace ChocolArm64 Set("x1111010010xxxxxxxxx10xxxxxxxxxx", AInstEmit.Ccmp, typeof(AOpCodeCcmpImm)); Set("x1111010010xxxxxxxxx00xxxxxxxxxx", AInstEmit.Ccmp, typeof(AOpCodeCcmpReg)); Set("11010101000000110011xxxx01011111", AInstEmit.Clrex, typeof(AOpCodeSystem)); + Set("x101101011000000000101xxxxxxxxxx", AInstEmit.Cls, typeof(AOpCodeAlu)); Set("x101101011000000000100xxxxxxxxxx", AInstEmit.Clz, typeof(AOpCodeAlu)); Set("x0011010110xxxxx010000xxxxxxxxxx", AInstEmit.Crc32b, typeof(AOpCodeAluRs)); Set("x0011010110xxxxx010001xxxxxxxxxx", AInstEmit.Crc32h, typeof(AOpCodeAluRs)); @@ -68,7 +69,7 @@ namespace ChocolArm64 Set("xx111000010xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Ldr, typeof(AOpCodeMemImm)); Set("xx11100101xxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Ldr, typeof(AOpCodeMemImm)); Set("xx111000011xxxxxxxxx10xxxxxxxxxx", AInstEmit.Ldr, typeof(AOpCodeMemReg)); - Set("xx011000xxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.LdrLit, typeof(AOpCodeMemLit)); + Set("xx011000xxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.LdrLit, typeof(AOpCodeMemLit)); Set("0x1110001x0xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Ldrs, typeof(AOpCodeMemImm)); Set("0x1110011xxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Ldrs, typeof(AOpCodeMemImm)); Set("10111000100xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Ldrs, typeof(AOpCodeMemImm)); @@ -91,6 +92,7 @@ namespace ChocolArm64 Set("x01100100xxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Orr, typeof(AOpCodeAluImm)); Set("x0101010xx0xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Orr, typeof(AOpCodeAluRs)); Set("1111100110xxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Pfrm, typeof(AOpCodeMemImm)); + Set("11111000100xxxxxxxxx00xxxxxxxxxx", AInstEmit.Pfrm, typeof(AOpCodeMemImm)); Set("11011000xxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Pfrm, typeof(AOpCodeMemLit)); Set("x101101011000000000000xxxxxxxxxx", AInstEmit.Rbit, typeof(AOpCodeAlu)); Set("11010110010xxxxx000000xxxxxxxxxx", AInstEmit.Ret, typeof(AOpCodeBReg)); @@ -131,14 +133,20 @@ namespace ChocolArm64 Set("10011011110xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Umulh, typeof(AOpCodeMul)); //Vector + Set("0101111011100000101110xxxxxxxxxx", AInstEmit.Abs_S, typeof(AOpCodeSimd)); + Set("0>001110<<100000101110xxxxxxxxxx", AInstEmit.Abs_V, typeof(AOpCodeSimd)); + Set("01011110111xxxxx100001xxxxxxxxxx", AInstEmit.Add_S, typeof(AOpCodeSimdReg)); Set("0>001110<<1xxxxx100001xxxxxxxxxx", AInstEmit.Add_V, typeof(AOpCodeSimdReg)); - Set("01011110xx110001101110xxxxxxxxxx", AInstEmit.Addp_S, typeof(AOpCodeSimd)); + Set("0x001110<<1xxxxx010000xxxxxxxxxx", AInstEmit.Addhn_V, typeof(AOpCodeSimdReg)); + Set("0101111011110001101110xxxxxxxxxx", AInstEmit.Addp_S, typeof(AOpCodeSimd)); Set("0>001110<<1xxxxx101111xxxxxxxxxx", AInstEmit.Addp_V, typeof(AOpCodeSimdReg)); Set("000011100x110001101110xxxxxxxxxx", AInstEmit.Addv_V, typeof(AOpCodeSimd)); Set("01001110<<110001101110xxxxxxxxxx", AInstEmit.Addv_V, typeof(AOpCodeSimd)); Set("0x001110001xxxxx000111xxxxxxxxxx", AInstEmit.And_V, typeof(AOpCodeSimdReg)); Set("0x001110011xxxxx000111xxxxxxxxxx", AInstEmit.Bic_V, typeof(AOpCodeSimdReg)); Set("0x10111100000xxx<101110<<1xxxxx100011xxxxxxxxxx", AInstEmit.Cmeq_V, typeof(AOpCodeSimdReg)); Set("0>001110<<100000100110xxxxxxxxxx", AInstEmit.Cmeq_V, typeof(AOpCodeSimd)); @@ -161,8 +169,25 @@ namespace ChocolArm64 Set("000111100x100000110000xxxxxxxxxx", AInstEmit.Fabs_S, typeof(AOpCodeSimd)); Set("000111100x1xxxxx001010xxxxxxxxxx", AInstEmit.Fadd_S, typeof(AOpCodeSimdReg)); Set("0>0011100<1xxxxx110101xxxxxxxxxx", AInstEmit.Fadd_V, typeof(AOpCodeSimdReg)); + Set("0>1011100<1xxxxx110101xxxxxxxxxx", AInstEmit.Faddp_V, typeof(AOpCodeSimdReg)); Set("000111100x1xxxxxxxxx01xxxxx0xxxx", AInstEmit.Fccmp_S, typeof(AOpCodeSimdFcond)); Set("000111100x1xxxxxxxxx01xxxxx1xxxx", AInstEmit.Fccmpe_S, typeof(AOpCodeSimdFcond)); + Set("010111100x1xxxxx111001xxxxxxxxxx", AInstEmit.Fcmeq_S, typeof(AOpCodeSimdReg)); + Set("0>0011100<1xxxxx111001xxxxxxxxxx", AInstEmit.Fcmeq_V, typeof(AOpCodeSimdReg)); + Set("010111101x100000110110xxxxxxxxxx", AInstEmit.Fcmeq_S, typeof(AOpCodeSimd)); + Set("0>0011101<100000110110xxxxxxxxxx", AInstEmit.Fcmeq_V, typeof(AOpCodeSimd)); + Set("011111100x1xxxxx111001xxxxxxxxxx", AInstEmit.Fcmge_S, typeof(AOpCodeSimdReg)); + Set("0>1011100<1xxxxx111001xxxxxxxxxx", AInstEmit.Fcmge_V, typeof(AOpCodeSimdReg)); + Set("011111101x100000110010xxxxxxxxxx", AInstEmit.Fcmge_S, typeof(AOpCodeSimd)); + Set("0>1011101<100000110010xxxxxxxxxx", AInstEmit.Fcmge_V, typeof(AOpCodeSimd)); + Set("011111101x1xxxxx111001xxxxxxxxxx", AInstEmit.Fcmgt_S, typeof(AOpCodeSimdReg)); + Set("0>1011101<1xxxxx111001xxxxxxxxxx", AInstEmit.Fcmgt_V, typeof(AOpCodeSimdReg)); + Set("010111101x100000110010xxxxxxxxxx", AInstEmit.Fcmgt_S, typeof(AOpCodeSimd)); + Set("0>0011101<100000110010xxxxxxxxxx", AInstEmit.Fcmgt_V, typeof(AOpCodeSimd)); + Set("011111101x100000110110xxxxxxxxxx", AInstEmit.Fcmle_S, typeof(AOpCodeSimd)); + Set("0>1011101<100000110110xxxxxxxxxx", AInstEmit.Fcmle_V, typeof(AOpCodeSimd)); + Set("010111101x100000111010xxxxxxxxxx", AInstEmit.Fcmlt_S, typeof(AOpCodeSimd)); + Set("0>0011101<100000111010xxxxxxxxxx", AInstEmit.Fcmlt_V, typeof(AOpCodeSimd)); Set("000111100x1xxxxx001000xxxxx0x000", AInstEmit.Fcmp_S, typeof(AOpCodeSimdReg)); Set("000111100x1xxxxx001000xxxxx1x000", AInstEmit.Fcmpe_S, typeof(AOpCodeSimdReg)); Set("000111100x1xxxxxxxxx11xxxxxxxxxx", AInstEmit.Fcsel_S, typeof(AOpCodeSimdFcond)); @@ -182,16 +207,20 @@ namespace ChocolArm64 Set("x00111100x111001000000xxxxxxxxxx", AInstEmit.Fcvtzu_Gp, typeof(AOpCodeSimdCvt)); Set("x00111100x011001xxxxxxxxxxxxxxxx", AInstEmit.Fcvtzu_Gp_Fix, typeof(AOpCodeSimdCvt)); Set("0>1011101<100001101110xxxxxxxxxx", AInstEmit.Fcvtzu_V, typeof(AOpCodeSimd)); - Set("0x1011110>>xxxxx111111xxxxxxxxxx", AInstEmit.Fcvtzu_V, typeof(AOpCodeSimdShImm)); + Set("0x1011110>>xxxxx111111xxxxxxxxxx", AInstEmit.Fcvtzu_V, typeof(AOpCodeSimdShImm)); Set("000111100x1xxxxx000110xxxxxxxxxx", AInstEmit.Fdiv_S, typeof(AOpCodeSimdReg)); Set("0>1011100<1xxxxx111111xxxxxxxxxx", AInstEmit.Fdiv_V, typeof(AOpCodeSimdReg)); Set("000111110x0xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Fmadd_S, typeof(AOpCodeSimdReg)); Set("000111100x1xxxxx010010xxxxxxxxxx", AInstEmit.Fmax_S, typeof(AOpCodeSimdReg)); + Set("0x0011100x1xxxxx111101xxxxxxxxxx", AInstEmit.Fmax_V, typeof(AOpCodeSimdReg)); Set("000111100x1xxxxx011010xxxxxxxxxx", AInstEmit.Fmaxnm_S, typeof(AOpCodeSimdReg)); Set("000111100x1xxxxx010110xxxxxxxxxx", AInstEmit.Fmin_S, typeof(AOpCodeSimdReg)); + Set("0x0011101x1xxxxx111101xxxxxxxxxx", AInstEmit.Fmin_V, typeof(AOpCodeSimdReg)); Set("000111100x1xxxxx011110xxxxxxxxxx", AInstEmit.Fminnm_S, typeof(AOpCodeSimdReg)); Set("0>0011100<1xxxxx110011xxxxxxxxxx", AInstEmit.Fmla_V, typeof(AOpCodeSimdReg)); Set("0x0011111<0011101<1xxxxx110011xxxxxxxxxx", AInstEmit.Fmls_V, typeof(AOpCodeSimdReg)); + Set("0x0011111<1011100<1xxxxx110111xxxxxxxxxx", AInstEmit.Fmul_V, typeof(AOpCodeSimdReg)); Set("0x0011111<1011101<100000111110xxxxxxxxxx", AInstEmit.Fneg_V, typeof(AOpCodeSimd)); + Set("000111110x1xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Fnmadd_S, typeof(AOpCodeSimdReg)); Set("000111110x1xxxxx1xxxxxxxxxxxxxxx", AInstEmit.Fnmsub_S, typeof(AOpCodeSimdReg)); Set("000111100x1xxxxx100010xxxxxxxxxx", AInstEmit.Fnmul_S, typeof(AOpCodeSimdReg)); + Set("010111101x100001110110xxxxxxxxxx", AInstEmit.Frecpe_S, typeof(AOpCodeSimd)); + Set("0>0011101<100001110110xxxxxxxxxx", AInstEmit.Frecpe_V, typeof(AOpCodeSimd)); + Set("010111100x1xxxxx111111xxxxxxxxxx", AInstEmit.Frecps_S, typeof(AOpCodeSimdReg)); + Set("0>0011100<1xxxxx111111xxxxxxxxxx", AInstEmit.Frecps_V, typeof(AOpCodeSimdReg)); Set("000111100x100110010000xxxxxxxxxx", AInstEmit.Frinta_S, typeof(AOpCodeSimd)); + Set("0>1011100<100001100010xxxxxxxxxx", AInstEmit.Frinta_V, typeof(AOpCodeSimd)); + Set("000111100x100111110000xxxxxxxxxx", AInstEmit.Frinti_S, typeof(AOpCodeSimd)); + Set("0>1011101<100001100110xxxxxxxxxx", AInstEmit.Frinti_V, typeof(AOpCodeSimd)); Set("000111100x100101010000xxxxxxxxxx", AInstEmit.Frintm_S, typeof(AOpCodeSimd)); Set("0>0011100<100001100110xxxxxxxxxx", AInstEmit.Frintm_V, typeof(AOpCodeSimd)); + Set("000111100x100100010000xxxxxxxxxx", AInstEmit.Frintn_S, typeof(AOpCodeSimd)); + Set("0>0011100<100001100010xxxxxxxxxx", AInstEmit.Frintn_V, typeof(AOpCodeSimd)); Set("000111100x100100110000xxxxxxxxxx", AInstEmit.Frintp_S, typeof(AOpCodeSimd)); + Set("0>0011101<100001100010xxxxxxxxxx", AInstEmit.Frintp_V, typeof(AOpCodeSimd)); Set("000111100x100111010000xxxxxxxxxx", AInstEmit.Frintx_S, typeof(AOpCodeSimd)); + Set("0>1011100<100001100110xxxxxxxxxx", AInstEmit.Frintx_V, typeof(AOpCodeSimd)); + Set("011111101x100001110110xxxxxxxxxx", AInstEmit.Frsqrte_S, typeof(AOpCodeSimd)); + Set("0>1011101<100001110110xxxxxxxxxx", AInstEmit.Frsqrte_V, typeof(AOpCodeSimd)); + Set("010111101x1xxxxx111111xxxxxxxxxx", AInstEmit.Frsqrts_S, typeof(AOpCodeSimdReg)); + Set("0>0011101<1xxxxx111111xxxxxxxxxx", AInstEmit.Frsqrts_V, typeof(AOpCodeSimdReg)); Set("000111100x100001110000xxxxxxxxxx", AInstEmit.Fsqrt_S, typeof(AOpCodeSimd)); Set("000111100x1xxxxx001110xxxxxxxxxx", AInstEmit.Fsub_S, typeof(AOpCodeSimdReg)); Set("0>0011101<1xxxxx110101xxxxxxxxxx", AInstEmit.Fsub_V, typeof(AOpCodeSimdReg)); @@ -218,8 +265,8 @@ namespace ChocolArm64 Set("01101110000xxxxx0xxxx1xxxxxxxxxx", AInstEmit.Ins_V, typeof(AOpCodeSimdIns)); Set("0x00110001000000xxxxxxxxxxxxxxxx", AInstEmit.Ld__Vms, typeof(AOpCodeSimdMemMs)); Set("0x001100110xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Ld__Vms, typeof(AOpCodeSimdMemMs)); - Set("0x00110101000000xx0xxxxxxxxxxxxx", AInstEmit.Ld__Vss, typeof(AOpCodeSimdMemSs)); - Set("0x001101110xxxxxxx0xxxxxxxxxxxxx", AInstEmit.Ld__Vss, typeof(AOpCodeSimdMemSs)); + Set("0x00110101x00000xxxxxxxxxxxxxxxx", AInstEmit.Ld__Vss, typeof(AOpCodeSimdMemSs)); + Set("0x00110111xxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Ld__Vss, typeof(AOpCodeSimdMemSs)); Set("xx10110xx1xxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Ldp, typeof(AOpCodeSimdMemPair)); Set("xx111100x10xxxxxxxxx00xxxxxxxxxx", AInstEmit.Ldr, typeof(AOpCodeSimdMemImm)); Set("xx111100x10xxxxxxxxx01xxxxxxxxxx", AInstEmit.Ldr, typeof(AOpCodeSimdMemImm)); @@ -228,6 +275,7 @@ namespace ChocolArm64 Set("xx111100x11xxxxxxxxx10xxxxxxxxxx", AInstEmit.Ldr, typeof(AOpCodeSimdMemReg)); Set("xx011100xxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.LdrLit, typeof(AOpCodeSimdMemLit)); Set("0x001110<<1xxxxx100101xxxxxxxxxx", AInstEmit.Mla_V, typeof(AOpCodeSimdReg)); + Set("0x101111xxxxxxxx0000x0xxxxxxxxxx", AInstEmit.Mla_Ve, typeof(AOpCodeSimdRegElem)); Set("0x101110<<1xxxxx100101xxxxxxxxxx", AInstEmit.Mls_V, typeof(AOpCodeSimdReg)); Set("0x00111100000xxx0xx001xxxxxxxxxx", AInstEmit.Movi_V, typeof(AOpCodeSimdImm)); Set("0x00111100000xxx10x001xxxxxxxxxx", AInstEmit.Movi_V, typeof(AOpCodeSimdImm)); @@ -238,11 +286,14 @@ namespace ChocolArm64 Set("0x10111100000xxx0xx001xxxxxxxxxx", AInstEmit.Mvni_V, typeof(AOpCodeSimdImm)); Set("0x10111100000xxx10x001xxxxxxxxxx", AInstEmit.Mvni_V, typeof(AOpCodeSimdImm)); Set("0x10111100000xxx110x01xxxxxxxxxx", AInstEmit.Mvni_V, typeof(AOpCodeSimdImm)); - Set("0>101110<<100000101110xxxxxxxxxx", AInstEmit.Neg_V, typeof(AOpCodeSimdReg)); + Set("0111111011100000101110xxxxxxxxxx", AInstEmit.Neg_S, typeof(AOpCodeSimd)); + Set("0>101110<<100000101110xxxxxxxxxx", AInstEmit.Neg_V, typeof(AOpCodeSimd)); Set("0x10111000100000010110xxxxxxxxxx", AInstEmit.Not_V, typeof(AOpCodeSimd)); Set("0x001110101xxxxx000111xxxxxxxxxx", AInstEmit.Orr_V, typeof(AOpCodeSimdReg)); Set("0x00111100000xxx<>>>xxx000101xxxxxxxxxx", AInstEmit.Ssra_V, typeof(AOpCodeSimdShImm)); Set("0x00110000000000xxxxxxxxxxxxxxxx", AInstEmit.St__Vms, typeof(AOpCodeSimdMemMs)); Set("0x001100100xxxxxxxxxxxxxxxxxxxxx", AInstEmit.St__Vms, typeof(AOpCodeSimdMemMs)); - Set("0x00110100000000xx0xxxxxxxxxxxxx", AInstEmit.St__Vss, typeof(AOpCodeSimdMemSs)); - Set("0x001101100xxxxxxx0xxxxxxxxxxxxx", AInstEmit.St__Vss, typeof(AOpCodeSimdMemSs)); + Set("0x00110100x00000xxxxxxxxxxxxxxxx", AInstEmit.St__Vss, typeof(AOpCodeSimdMemSs)); + Set("0x00110110xxxxxxxxxxxxxxxxxxxxxx", AInstEmit.St__Vss, typeof(AOpCodeSimdMemSs)); Set("xx10110xx0xxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Stp, typeof(AOpCodeSimdMemPair)); Set("xx111100x00xxxxxxxxx00xxxxxxxxxx", AInstEmit.Str, typeof(AOpCodeSimdMemImm)); Set("xx111100x00xxxxxxxxx01xxxxxxxxxx", AInstEmit.Str, typeof(AOpCodeSimdMemImm)); Set("xx111100x00xxxxxxxxx11xxxxxxxxxx", AInstEmit.Str, typeof(AOpCodeSimdMemImm)); Set("xx111101x0xxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Str, typeof(AOpCodeSimdMemImm)); Set("xx111100x01xxxxxxxxx10xxxxxxxxxx", AInstEmit.Str, typeof(AOpCodeSimdMemReg)); - Set("01111110xx1xxxxx100001xxxxxxxxxx", AInstEmit.Sub_S, typeof(AOpCodeSimdReg)); + Set("01111110111xxxxx100001xxxxxxxxxx", AInstEmit.Sub_S, typeof(AOpCodeSimdReg)); Set("0>101110<<1xxxxx100001xxxxxxxxxx", AInstEmit.Sub_V, typeof(AOpCodeSimdReg)); + Set("0x001110<<1xxxxx011000xxxxxxxxxx", AInstEmit.Subhn_V, typeof(AOpCodeSimdReg)); Set("0x001110000xxxxx0xx000xxxxxxxxxx", AInstEmit.Tbl_V, typeof(AOpCodeSimdTbl)); + Set("0>001110<<0xxxxx001010xxxxxxxxxx", AInstEmit.Trn1_V, typeof(AOpCodeSimdReg)); + Set("0>001110<<0xxxxx011010xxxxxxxxxx", AInstEmit.Trn2_V, typeof(AOpCodeSimdReg)); + Set("0x101110<<1xxxxx011101xxxxxxxxxx", AInstEmit.Uabd_V, typeof(AOpCodeSimdReg)); + Set("0x101110<<1xxxxx011100xxxxxxxxxx", AInstEmit.Uabdl_V, typeof(AOpCodeSimdReg)); + Set("0x101110<<1xxxxx000000xxxxxxxxxx", AInstEmit.Uaddl_V, typeof(AOpCodeSimdReg)); Set("001011100x110000001110xxxxxxxxxx", AInstEmit.Uaddlv_V, typeof(AOpCodeSimd)); Set("01101110<<110000001110xxxxxxxxxx", AInstEmit.Uaddlv_V, typeof(AOpCodeSimd)); Set("0x101110<<1xxxxx000100xxxxxxxxxx", AInstEmit.Uaddw_V, typeof(AOpCodeSimdReg)); Set("x0011110xx100011000000xxxxxxxxxx", AInstEmit.Ucvtf_Gp, typeof(AOpCodeSimdCvt)); Set("011111100x100001110110xxxxxxxxxx", AInstEmit.Ucvtf_S, typeof(AOpCodeSimd)); Set("0x1011100x100001110110xxxxxxxxxx", AInstEmit.Ucvtf_V, typeof(AOpCodeSimd)); + Set("0x101110<<1xxxxx000001xxxxxxxxxx", AInstEmit.Uhadd_V, typeof(AOpCodeSimdReg)); Set("0x001110000xxxxx001111xxxxxxxxxx", AInstEmit.Umov_S, typeof(AOpCodeSimdIns)); Set("0x101110<<1xxxxx110000xxxxxxxxxx", AInstEmit.Umull_V, typeof(AOpCodeSimdReg)); Set("0>101110<<1xxxxx010001xxxxxxxxxx", AInstEmit.Ushl_V, typeof(AOpCodeSimdReg)); @@ -287,11 +345,11 @@ namespace ChocolArm64 Set("011111110>>>>xxx000001xxxxxxxxxx", AInstEmit.Ushr_S, typeof(AOpCodeSimdShImm)); Set("0x1011110>>>>xxx000001xxxxxxxxxx", AInstEmit.Ushr_V, typeof(AOpCodeSimdShImm)); Set("0x1011110>>>>xxx000101xxxxxxxxxx", AInstEmit.Usra_V, typeof(AOpCodeSimdShImm)); - Set("0x001110xx0xxxxx000110xxxxxxxxxx", AInstEmit.Uzp1_V, typeof(AOpCodeSimdReg)); - Set("0x001110xx0xxxxx010110xxxxxxxxxx", AInstEmit.Uzp2_V, typeof(AOpCodeSimdReg)); + Set("0>001110<<0xxxxx000110xxxxxxxxxx", AInstEmit.Uzp1_V, typeof(AOpCodeSimdReg)); + Set("0>001110<<0xxxxx010110xxxxxxxxxx", AInstEmit.Uzp2_V, typeof(AOpCodeSimdReg)); Set("0x001110<<100001001010xxxxxxxxxx", AInstEmit.Xtn_V, typeof(AOpCodeSimd)); - Set("0x001110xx0xxxxx001110xxxxxxxxxx", AInstEmit.Zip1_V, typeof(AOpCodeSimdReg)); - Set("0x001110xx0xxxxx011110xxxxxxxxxx", AInstEmit.Zip2_V, typeof(AOpCodeSimdReg)); + Set("0>001110<<0xxxxx001110xxxxxxxxxx", AInstEmit.Zip1_V, typeof(AOpCodeSimdReg)); + Set("0>001110<<0xxxxx011110xxxxxxxxxx", AInstEmit.Zip2_V, typeof(AOpCodeSimdReg)); #endregion } @@ -433,4 +491,4 @@ namespace ChocolArm64 return AInst.Undefined; } } -} \ No newline at end of file +} diff --git a/ChocolArm64/AOptimizations.cs b/ChocolArm64/AOptimizations.cs index a3c82dccd..2627c2368 100644 --- a/ChocolArm64/AOptimizations.cs +++ b/ChocolArm64/AOptimizations.cs @@ -1,4 +1,6 @@ public static class AOptimizations { public static bool DisableMemoryChecks = false; + + public static bool GenerateCallStack = true; } \ No newline at end of file diff --git a/ChocolArm64/AThread.cs b/ChocolArm64/AThread.cs index 62f9d2d39..16804a7c5 100644 --- a/ChocolArm64/AThread.cs +++ b/ChocolArm64/AThread.cs @@ -54,6 +54,14 @@ namespace ChocolArm64 return true; } - public void StopExecution() => ThreadState.Running = false; + public void StopExecution() + { + ThreadState.Running = false; + } + + public bool IsCurrentThread() + { + return Thread.CurrentThread == Work; + } } } \ No newline at end of file diff --git a/ChocolArm64/ATranslatedSub.cs b/ChocolArm64/ATranslatedSub.cs index 414038ab6..9dbc378ec 100644 --- a/ChocolArm64/ATranslatedSub.cs +++ b/ChocolArm64/ATranslatedSub.cs @@ -3,6 +3,7 @@ using ChocolArm64.State; using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Linq; using System.Reflection; using System.Reflection.Emit; @@ -23,7 +24,7 @@ namespace ChocolArm64 public ReadOnlyCollection Params { get; private set; } - private HashSet Callees; + private HashSet Callers; private ATranslatedSubType Type; @@ -33,7 +34,7 @@ namespace ChocolArm64 private int MinCallCountForReJit = 250; - public ATranslatedSub(DynamicMethod Method, List Params, HashSet Callees) + public ATranslatedSub(DynamicMethod Method, List Params) { if (Method == null) { @@ -45,14 +46,10 @@ namespace ChocolArm64 throw new ArgumentNullException(nameof(Params)); } - if (Callees == null) - { - throw new ArgumentNullException(nameof(Callees)); - } - this.Method = Method; this.Params = Params.AsReadOnly(); - this.Callees = Callees; + + Callers = new HashSet(); PrepareDelegate(); } @@ -107,17 +104,14 @@ namespace ChocolArm64 public bool ShouldReJit() { - if (Type == ATranslatedSubType.SubTier0) + if (NeedsReJit && CallCount < MinCallCountForReJit) { - if (CallCount < MinCallCountForReJit) - { - CallCount++; - } + CallCount++; - return CallCount == MinCallCountForReJit; + return false; } - return Type == ATranslatedSubType.SubTier1 && NeedsReJit; + return NeedsReJit; } public long Execute(AThreadState ThreadState, AMemory Memory) @@ -125,10 +119,32 @@ namespace ChocolArm64 return ExecDelegate(ThreadState, Memory); } - public void SetType(ATranslatedSubType Type) => this.Type = Type; + public void AddCaller(long Position) + { + lock (Callers) + { + Callers.Add(Position); + } + } - public bool HasCallee(long Position) => Callees.Contains(Position); + public long[] GetCallerPositions() + { + lock (Callers) + { + return Callers.ToArray(); + } + } - public void MarkForReJit() => NeedsReJit = true; + public void SetType(ATranslatedSubType Type) + { + this.Type = Type; + + if (Type == ATranslatedSubType.SubTier0) + { + NeedsReJit = true; + } + } + + public void MarkForReJit() => NeedsReJit = true; } } \ No newline at end of file diff --git a/ChocolArm64/ATranslator.cs b/ChocolArm64/ATranslator.cs index 02c18efd2..e46750fce 100644 --- a/ChocolArm64/ATranslator.cs +++ b/ChocolArm64/ATranslator.cs @@ -107,25 +107,31 @@ namespace ChocolArm64 ATranslatedSub Subroutine = Context.GetSubroutine(); - if (SubBlocks.Contains(Position)) + lock (SubBlocks) { - SubBlocks.Remove(Position); + if (SubBlocks.Contains(Position)) + { + SubBlocks.Remove(Position); - Subroutine.SetType(ATranslatedSubType.SubBlock); - } - else - { - Subroutine.SetType(ATranslatedSubType.SubTier0); + Subroutine.SetType(ATranslatedSubType.SubBlock); + } + else + { + Subroutine.SetType(ATranslatedSubType.SubTier0); + } } CachedSubs.AddOrUpdate(Position, Subroutine, (Key, OldVal) => Subroutine); AOpCode LastOp = Block.GetLastOp(); - if (LastOp.Emitter != AInstEmit.Ret && - LastOp.Emitter != AInstEmit.Br) + lock (SubBlocks) { - SubBlocks.Add(LastOp.Position + 4); + if (LastOp.Emitter != AInstEmit.Ret && + LastOp.Emitter != AInstEmit.Br) + { + SubBlocks.Add(LastOp.Position + 4); + } } return Subroutine; @@ -154,11 +160,14 @@ namespace ChocolArm64 //Mark all methods that calls this method for ReJiting, //since we can now call it directly which is faster. - foreach (ATranslatedSub TS in CachedSubs.Values) + if (CachedSubs.TryGetValue(Position, out ATranslatedSub OldSub)) { - if (TS.HasCallee(Position)) + foreach (long CallerPos in OldSub.GetCallerPositions()) { - TS.MarkForReJit(); + if (CachedSubs.TryGetValue(Position, out ATranslatedSub CallerSub)) + { + CallerSub.MarkForReJit(); + } } } diff --git a/ChocolArm64/Decoder/AOpCodeBImmCmp.cs b/ChocolArm64/Decoder/AOpCodeBImmCmp.cs index 1b6185da6..0f16b73e0 100644 --- a/ChocolArm64/Decoder/AOpCodeBImmCmp.cs +++ b/ChocolArm64/Decoder/AOpCodeBImmCmp.cs @@ -1,4 +1,5 @@ using ChocolArm64.Instruction; +using ChocolArm64.State; namespace ChocolArm64.Decoder { @@ -11,6 +12,10 @@ namespace ChocolArm64.Decoder Rt = OpCode & 0x1f; Imm = Position + ADecoderHelper.DecodeImmS19_2(OpCode); + + RegisterSize = (OpCode >> 31) != 0 + ? ARegisterSize.Int64 + : ARegisterSize.Int32; } } } \ No newline at end of file diff --git a/ChocolArm64/Decoder/AOpCodeSimdMemMs.cs b/ChocolArm64/Decoder/AOpCodeSimdMemMs.cs index 9ea979ba7..a54e2360c 100644 --- a/ChocolArm64/Decoder/AOpCodeSimdMemMs.cs +++ b/ChocolArm64/Decoder/AOpCodeSimdMemMs.cs @@ -25,8 +25,8 @@ namespace ChocolArm64.Decoder default: Inst = AInst.Undefined; return; } - Size = (OpCode >> 10) & 0x3; - WBack = ((OpCode >> 23) & 0x1) != 0; + Size = (OpCode >> 10) & 3; + WBack = ((OpCode >> 23) & 1) != 0; bool Q = ((OpCode >> 30) & 1) != 0; diff --git a/ChocolArm64/Decoder/AOpCodeSimdMemSs.cs b/ChocolArm64/Decoder/AOpCodeSimdMemSs.cs index be4a8cd98..c8794ff5a 100644 --- a/ChocolArm64/Decoder/AOpCodeSimdMemSs.cs +++ b/ChocolArm64/Decoder/AOpCodeSimdMemSs.cs @@ -18,7 +18,7 @@ namespace ChocolArm64.Decoder int Scale = (OpCode >> 14) & 3; int L = (OpCode >> 22) & 1; int Q = (OpCode >> 30) & 1; - + SElems |= (OpCode >> 21) & 1; SElems++; @@ -82,12 +82,13 @@ namespace ChocolArm64.Decoder } } + this.Index = Index; this.SElems = SElems; this.Size = Scale; Extend64 = false; - WBack = ((OpCode >> 23) & 0x1) != 0; + WBack = ((OpCode >> 23) & 1) != 0; RegisterSize = Q != 0 ? ARegisterSize.SIMD128 diff --git a/ChocolArm64/Decoder/AOpCodeSimdRegElem.cs b/ChocolArm64/Decoder/AOpCodeSimdRegElem.cs index 721da88fa..d6dc4bd23 100644 --- a/ChocolArm64/Decoder/AOpCodeSimdRegElem.cs +++ b/ChocolArm64/Decoder/AOpCodeSimdRegElem.cs @@ -11,9 +11,8 @@ namespace ChocolArm64.Decoder switch (Size) { case 1: - Index = (OpCode >> 21) & 1 | - (OpCode >> 10) & 2 | - (OpCode >> 18) & 4; + Index = (OpCode >> 20) & 3 | + (OpCode >> 9) & 4; Rm &= 0xf; diff --git a/ChocolArm64/Instruction/AInstEmitAlu.cs b/ChocolArm64/Instruction/AInstEmitAlu.cs index 57020364b..bacbfc9e8 100644 --- a/ChocolArm64/Instruction/AInstEmitAlu.cs +++ b/ChocolArm64/Instruction/AInstEmitAlu.cs @@ -100,6 +100,24 @@ namespace ChocolArm64.Instruction EmitDataStore(Context, SetFlags); } + public static void Cls(AILEmitterCtx Context) + { + AOpCodeAlu Op = (AOpCodeAlu)Context.CurrOp; + + Context.EmitLdintzr(Op.Rn); + + if (Op.RegisterSize == ARegisterSize.Int32) + { + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.CountLeadingSigns32)); + } + else + { + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.CountLeadingSigns64)); + } + + Context.EmitStintzr(Op.Rd); + } + public static void Clz(AILEmitterCtx Context) { AOpCodeAlu Op = (AOpCodeAlu)Context.CurrOp; @@ -383,4 +401,4 @@ namespace ChocolArm64.Instruction Context.EmitStflg((int)APState.CBit); } } -} \ No newline at end of file +} diff --git a/ChocolArm64/Instruction/AInstEmitException.cs b/ChocolArm64/Instruction/AInstEmitException.cs index 3964c9497..3e444c730 100644 --- a/ChocolArm64/Instruction/AInstEmitException.cs +++ b/ChocolArm64/Instruction/AInstEmitException.cs @@ -1,15 +1,12 @@ using ChocolArm64.Decoder; using ChocolArm64.State; using ChocolArm64.Translation; -using System.Reflection; using System.Reflection.Emit; namespace ChocolArm64.Instruction { static partial class AInstEmit { - private const BindingFlags Binding = BindingFlags.NonPublic | BindingFlags.Instance; - public static void Brk(AILEmitterCtx Context) { EmitExceptionCall(Context, nameof(AThreadState.OnBreak)); @@ -30,9 +27,7 @@ namespace ChocolArm64.Instruction Context.EmitLdc_I4(Op.Id); - MethodInfo MthdInfo = typeof(AThreadState).GetMethod(MthdName, Binding); - - Context.EmitCall(MthdInfo); + Context.EmitPrivateCall(typeof(AThreadState), MthdName); //Check if the thread should still be running, if it isn't then we return 0 //to force a return to the dispatcher and then exit the thread. @@ -73,11 +68,7 @@ namespace ChocolArm64.Instruction Context.EmitLdc_I8(Op.Position); Context.EmitLdc_I4(Op.RawOpCode); - string MthdName = nameof(AThreadState.OnUndefined); - - MethodInfo MthdInfo = typeof(AThreadState).GetMethod(MthdName, Binding); - - Context.EmitCall(MthdInfo); + Context.EmitPrivateCall(typeof(AThreadState), nameof(AThreadState.OnUndefined)); if (Context.CurrBlock.Next != null) { diff --git a/ChocolArm64/Instruction/AInstEmitFlow.cs b/ChocolArm64/Instruction/AInstEmitFlow.cs index 91262834f..89979d050 100644 --- a/ChocolArm64/Instruction/AInstEmitFlow.cs +++ b/ChocolArm64/Instruction/AInstEmitFlow.cs @@ -35,6 +35,14 @@ namespace ChocolArm64.Instruction { AOpCodeBImmAl Op = (AOpCodeBImmAl)Context.CurrOp; + if (AOptimizations.GenerateCallStack) + { + Context.EmitLdarg(ATranslatedSub.StateArgIdx); + Context.EmitLdc_I8(Op.Imm); + + Context.EmitPrivateCall(typeof(AThreadState), nameof(AThreadState.EnterMethod)); + } + Context.EmitLdc_I(Op.Position + 4); Context.EmitStint(AThreadState.LRIndex); Context.EmitStoreState(); @@ -72,6 +80,14 @@ namespace ChocolArm64.Instruction { AOpCodeBReg Op = (AOpCodeBReg)Context.CurrOp; + if (AOptimizations.GenerateCallStack) + { + Context.EmitLdarg(ATranslatedSub.StateArgIdx); + Context.EmitLdintzr(Op.Rn); + + Context.EmitPrivateCall(typeof(AThreadState), nameof(AThreadState.EnterMethod)); + } + Context.EmitLdc_I(Op.Position + 4); Context.EmitStint(AThreadState.LRIndex); Context.EmitStoreState(); @@ -84,6 +100,14 @@ namespace ChocolArm64.Instruction { AOpCodeBReg Op = (AOpCodeBReg)Context.CurrOp; + if (AOptimizations.GenerateCallStack) + { + Context.EmitLdarg(ATranslatedSub.StateArgIdx); + Context.EmitLdintzr(Op.Rn); + + Context.EmitPrivateCall(typeof(AThreadState), nameof(AThreadState.JumpMethod)); + } + Context.EmitStoreState(); Context.EmitLdintzr(Op.Rn); @@ -105,6 +129,13 @@ namespace ChocolArm64.Instruction public static void Ret(AILEmitterCtx Context) { + if (AOptimizations.GenerateCallStack) + { + Context.EmitLdarg(ATranslatedSub.StateArgIdx); + + Context.EmitPrivateCall(typeof(AThreadState), nameof(AThreadState.ExitMethod)); + } + Context.EmitStoreState(); Context.EmitLdint(AThreadState.LRIndex); diff --git a/ChocolArm64/Instruction/AInstEmitSimdArithmetic.cs b/ChocolArm64/Instruction/AInstEmitSimdArithmetic.cs index bf980a581..2dce74107 100644 --- a/ChocolArm64/Instruction/AInstEmitSimdArithmetic.cs +++ b/ChocolArm64/Instruction/AInstEmitSimdArithmetic.cs @@ -11,11 +11,44 @@ namespace ChocolArm64.Instruction { static partial class AInstEmit { + public static void Abs_S(AILEmitterCtx Context) + { + EmitScalarUnaryOpSx(Context, () => EmitAbs(Context)); + } + + public static void Abs_V(AILEmitterCtx Context) + { + EmitVectorUnaryOpSx(Context, () => EmitAbs(Context)); + } + + private static void EmitAbs(AILEmitterCtx Context) + { + AILLabel LblTrue = new AILLabel(); + + Context.Emit(OpCodes.Dup); + Context.Emit(OpCodes.Ldc_I4_0); + Context.Emit(OpCodes.Bge_S, LblTrue); + + Context.Emit(OpCodes.Neg); + + Context.MarkLabel(LblTrue); + } + + public static void Add_S(AILEmitterCtx Context) + { + EmitScalarBinaryOpZx(Context, () => Context.Emit(OpCodes.Add)); + } + public static void Add_V(AILEmitterCtx Context) { EmitVectorBinaryOpZx(Context, () => Context.Emit(OpCodes.Add)); } + public static void Addhn_V(AILEmitterCtx Context) + { + EmitHighNarrow(Context, () => Context.Emit(OpCodes.Add), Round: false); + } + public static void Addp_S(AILEmitterCtx Context) { AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; @@ -101,6 +134,40 @@ namespace ChocolArm64.Instruction } } + private static void EmitHighNarrow(AILEmitterCtx Context, Action Emit, bool Round) + { + AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; + + int Elems = 8 >> Op.Size; + int ESize = 8 << Op.Size; + + int Part = Op.RegisterSize == ARegisterSize.SIMD128 ? Elems : 0; + + for (int Index = 0; Index < Elems; Index++) + { + EmitVectorExtractZx(Context, Op.Rn, Index, Op.Size + 1); + EmitVectorExtractZx(Context, Op.Rm, Index, Op.Size + 1); + + Emit(); + + if (Round) + { + Context.EmitLdc_I8(1L << (ESize - 1)); + + Context.Emit(OpCodes.Add); + } + + Context.EmitLsr(ESize); + + EmitVectorInsert(Context, Op.Rd, Part + Index, Op.Size); + } + + if (Part == 0) + { + EmitVectorZeroUpper(Context, Op.Rd); + } + } + public static void Fabd_S(AILEmitterCtx Context) { EmitScalarBinaryOpF(Context, () => @@ -129,6 +196,38 @@ namespace ChocolArm64.Instruction EmitVectorBinaryOpF(Context, () => Context.Emit(OpCodes.Add)); } + public static void Faddp_V(AILEmitterCtx Context) + { + AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; + + int SizeF = Op.Size & 1; + + int Bytes = Context.CurrOp.GetBitsCount() >> 3; + + int Elems = Bytes >> SizeF + 2; + int Half = Elems >> 1; + + for (int Index = 0; Index < Elems; Index++) + { + int Elem = (Index & (Half - 1)) << 1; + + EmitVectorExtractF(Context, Index < Half ? Op.Rn : Op.Rm, Elem + 0, SizeF); + EmitVectorExtractF(Context, Index < Half ? Op.Rn : Op.Rm, Elem + 1, SizeF); + + Context.Emit(OpCodes.Add); + + EmitVectorInsertTmpF(Context, Index, SizeF); + } + + Context.EmitLdvectmp(); + Context.EmitStvec(Op.Rd); + + if (Op.RegisterSize == ARegisterSize.SIMD64) + { + EmitVectorZeroUpper(Context, Op.Rd); + } + } + public static void Fdiv_S(AILEmitterCtx Context) { EmitScalarBinaryOpF(Context, () => Context.Emit(OpCodes.Div)); @@ -150,17 +249,87 @@ namespace ChocolArm64.Instruction public static void Fmax_S(AILEmitterCtx Context) { + AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; + EmitScalarBinaryOpF(Context, () => { - EmitBinaryMathCall(Context, nameof(Math.Max)); + if (Op.Size == 0) + { + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.MaxF)); + } + else if (Op.Size == 1) + { + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.Max)); + } + else + { + throw new InvalidOperationException(); + } + }); + } + + public static void Fmax_V(AILEmitterCtx Context) + { + AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; + + EmitVectorBinaryOpF(Context, () => + { + if (Op.Size == 0) + { + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.MaxF)); + } + else if (Op.Size == 1) + { + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.Max)); + } + else + { + throw new InvalidOperationException(); + } }); } public static void Fmin_S(AILEmitterCtx Context) { + AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; + EmitScalarBinaryOpF(Context, () => { - EmitBinaryMathCall(Context, nameof(Math.Min)); + if (Op.Size == 0) + { + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.MinF)); + } + else if (Op.Size == 1) + { + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.Min)); + } + else + { + throw new InvalidOperationException(); + } + }); + } + + public static void Fmin_V(AILEmitterCtx Context) + { + AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; + + int SizeF = Op.Size & 1; + + EmitVectorBinaryOpF(Context, () => + { + if (SizeF == 0) + { + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.MinF)); + } + else if (SizeF == 1) + { + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.Min)); + } + else + { + throw new InvalidOperationException(); + } }); } @@ -192,6 +361,24 @@ namespace ChocolArm64.Instruction }); } + public static void Fmls_V(AILEmitterCtx Context) + { + EmitVectorTernaryOpF(Context, () => + { + Context.Emit(OpCodes.Mul); + Context.Emit(OpCodes.Sub); + }); + } + + public static void Fmls_Ve(AILEmitterCtx Context) + { + EmitVectorTernaryOpByElemF(Context, () => + { + Context.Emit(OpCodes.Mul); + Context.Emit(OpCodes.Sub); + }); + } + public static void Fmsub_S(AILEmitterCtx Context) { EmitScalarTernaryRaOpF(Context, () => @@ -206,6 +393,11 @@ namespace ChocolArm64.Instruction EmitScalarBinaryOpF(Context, () => Context.Emit(OpCodes.Mul)); } + public static void Fmul_Se(AILEmitterCtx Context) + { + EmitScalarBinaryOpByElemF(Context, () => Context.Emit(OpCodes.Mul)); + } + public static void Fmul_V(AILEmitterCtx Context) { EmitVectorBinaryOpF(Context, () => Context.Emit(OpCodes.Mul)); @@ -221,13 +413,30 @@ namespace ChocolArm64.Instruction EmitScalarUnaryOpF(Context, () => Context.Emit(OpCodes.Neg)); } - public static void Fnmul_S(AILEmitterCtx Context) + public static void Fneg_V(AILEmitterCtx Context) { - EmitScalarBinaryOpF(Context, () => - { - Context.Emit(OpCodes.Mul); - Context.Emit(OpCodes.Neg); - }); + EmitVectorUnaryOpF(Context, () => Context.Emit(OpCodes.Neg)); + } + + public static void Fnmadd_S(AILEmitterCtx Context) + { + AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; + + int SizeF = Op.Size & 1; + + EmitVectorExtractF(Context, Op.Rn, 0, SizeF); + + Context.Emit(OpCodes.Neg); + + EmitVectorExtractF(Context, Op.Rm, 0, SizeF); + + Context.Emit(OpCodes.Mul); + + EmitVectorExtractF(Context, Op.Ra, 0, SizeF); + + Context.Emit(OpCodes.Sub); + + EmitScalarSetF(Context, Op.Rd, SizeF); } public static void Fnmsub_S(AILEmitterCtx Context) @@ -235,7 +444,7 @@ namespace ChocolArm64.Instruction AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; int SizeF = Op.Size & 1; - + EmitVectorExtractF(Context, Op.Rn, 0, SizeF); EmitVectorExtractF(Context, Op.Rm, 0, SizeF); @@ -248,6 +457,119 @@ namespace ChocolArm64.Instruction EmitScalarSetF(Context, Op.Rd, SizeF); } + public static void Fnmul_S(AILEmitterCtx Context) + { + EmitScalarBinaryOpF(Context, () => + { + Context.Emit(OpCodes.Mul); + Context.Emit(OpCodes.Neg); + }); + } + + public static void Frecpe_S(AILEmitterCtx Context) + { + EmitFrecpe(Context, 0, Scalar: true); + } + + public static void Frecpe_V(AILEmitterCtx Context) + { + AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; + + int SizeF = Op.Size & 1; + + int Bytes = Context.CurrOp.GetBitsCount() >> 3; + + for (int Index = 0; Index < Bytes >> SizeF + 2; Index++) + { + EmitFrecpe(Context, Index, Scalar: false); + } + + if (Op.RegisterSize == ARegisterSize.SIMD64) + { + EmitVectorZeroUpper(Context, Op.Rd); + } + } + + private static void EmitFrecpe(AILEmitterCtx Context, int Index, bool Scalar) + { + AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; + + int SizeF = Op.Size & 1; + + if (SizeF == 0) + { + Context.EmitLdc_R4(1); + } + else /* if (SizeF == 1) */ + { + Context.EmitLdc_R8(1); + } + + EmitVectorExtractF(Context, Op.Rn, Index, SizeF); + + Context.Emit(OpCodes.Div); + + if (Scalar) + { + EmitVectorZeroAll(Context, Op.Rd); + } + + EmitVectorInsertF(Context, Op.Rd, Index, SizeF); + } + + public static void Frecps_S(AILEmitterCtx Context) + { + EmitFrecps(Context, 0, Scalar: true); + } + + public static void Frecps_V(AILEmitterCtx Context) + { + AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; + + int SizeF = Op.Size & 1; + + int Bytes = Context.CurrOp.GetBitsCount() >> 3; + + for (int Index = 0; Index < Bytes >> SizeF + 2; Index++) + { + EmitFrecps(Context, Index, Scalar: false); + } + + if (Op.RegisterSize == ARegisterSize.SIMD64) + { + EmitVectorZeroUpper(Context, Op.Rd); + } + } + + private static void EmitFrecps(AILEmitterCtx Context, int Index, bool Scalar) + { + AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; + + int SizeF = Op.Size & 1; + + if (SizeF == 0) + { + Context.EmitLdc_R4(2); + } + else /* if (SizeF == 1) */ + { + Context.EmitLdc_R8(2); + } + + EmitVectorExtractF(Context, Op.Rn, Index, SizeF); + EmitVectorExtractF(Context, Op.Rm, Index, SizeF); + + Context.Emit(OpCodes.Mul); + Context.Emit(OpCodes.Sub); + + if (Scalar) + { + EmitVectorZeroAll(Context, Op.Rd); + } + + EmitVectorInsertF(Context, Op.Rd, Index, SizeF); + } + public static void Frinta_S(AILEmitterCtx Context) { AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; @@ -259,6 +581,66 @@ namespace ChocolArm64.Instruction EmitScalarSetF(Context, Op.Rd, Op.Size); } + public static void Frinta_V(AILEmitterCtx Context) + { + EmitVectorUnaryOpF(Context, () => + { + EmitRoundMathCall(Context, MidpointRounding.AwayFromZero); + }); + } + + public static void Frinti_S(AILEmitterCtx Context) + { + AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; + + EmitScalarUnaryOpF(Context, () => + { + Context.EmitLdarg(ATranslatedSub.StateArgIdx); + + Context.EmitCallPropGet(typeof(AThreadState), nameof(AThreadState.Fpcr)); + + if (Op.Size == 0) + { + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.RoundF)); + } + else if (Op.Size == 1) + { + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.Round)); + } + else + { + throw new InvalidOperationException(); + } + }); + } + + public static void Frinti_V(AILEmitterCtx Context) + { + AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; + + int SizeF = Op.Size & 1; + + EmitVectorUnaryOpF(Context, () => + { + Context.EmitLdarg(ATranslatedSub.StateArgIdx); + + Context.EmitCallPropGet(typeof(AThreadState), nameof(AThreadState.Fpcr)); + + if (SizeF == 0) + { + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.RoundF)); + } + else if (SizeF == 1) + { + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.Round)); + } + else + { + throw new InvalidOperationException(); + } + }); + } + public static void Frintm_S(AILEmitterCtx Context) { EmitScalarUnaryOpF(Context, () => @@ -275,6 +657,25 @@ namespace ChocolArm64.Instruction }); } + public static void Frintn_S(AILEmitterCtx Context) + { + AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; + + EmitVectorExtractF(Context, Op.Rn, 0, Op.Size); + + EmitRoundMathCall(Context, MidpointRounding.ToEven); + + EmitScalarSetF(Context, Op.Rd, Op.Size); + } + + public static void Frintn_V(AILEmitterCtx Context) + { + EmitVectorUnaryOpF(Context, () => + { + EmitRoundMathCall(Context, MidpointRounding.ToEven); + }); + } + public static void Frintp_S(AILEmitterCtx Context) { EmitScalarUnaryOpF(Context, () => @@ -283,6 +684,14 @@ namespace ChocolArm64.Instruction }); } + public static void Frintp_V(AILEmitterCtx Context) + { + EmitVectorUnaryOpF(Context, () => + { + EmitUnaryMathCall(Context, nameof(Math.Ceiling)); + }); + } + public static void Frintx_S(AILEmitterCtx Context) { AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; @@ -308,6 +717,111 @@ namespace ChocolArm64.Instruction }); } + public static void Frintx_V(AILEmitterCtx Context) + { + AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; + + EmitVectorUnaryOpF(Context, () => + { + Context.EmitLdarg(ATranslatedSub.StateArgIdx); + + Context.EmitCallPropGet(typeof(AThreadState), nameof(AThreadState.Fpcr)); + + if (Op.Size == 0) + { + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.RoundF)); + } + else if (Op.Size == 1) + { + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.Round)); + } + else + { + throw new InvalidOperationException(); + } + }); + } + + public static void Frsqrte_S(AILEmitterCtx Context) + { + EmitScalarUnaryOpF(Context, () => + { + EmitUnarySoftFloatCall(Context, nameof(ASoftFloat.InvSqrtEstimate)); + }); + } + + public static void Frsqrte_V(AILEmitterCtx Context) + { + EmitVectorUnaryOpF(Context, () => + { + EmitUnarySoftFloatCall(Context, nameof(ASoftFloat.InvSqrtEstimate)); + }); + } + + public static void Frsqrts_S(AILEmitterCtx Context) + { + EmitFrsqrts(Context, 0, Scalar: true); + } + + public static void Frsqrts_V(AILEmitterCtx Context) + { + AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; + + int SizeF = Op.Size & 1; + + int Bytes = Context.CurrOp.GetBitsCount() >> 3; + + for (int Index = 0; Index < Bytes >> SizeF + 2; Index++) + { + EmitFrsqrts(Context, Index, Scalar: false); + } + + if (Op.RegisterSize == ARegisterSize.SIMD64) + { + EmitVectorZeroUpper(Context, Op.Rd); + } + } + + private static void EmitFrsqrts(AILEmitterCtx Context, int Index, bool Scalar) + { + AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; + + int SizeF = Op.Size & 1; + + if (SizeF == 0) + { + Context.EmitLdc_R4(3); + } + else /* if (SizeF == 1) */ + { + Context.EmitLdc_R8(3); + } + + EmitVectorExtractF(Context, Op.Rn, Index, SizeF); + EmitVectorExtractF(Context, Op.Rm, Index, SizeF); + + Context.Emit(OpCodes.Mul); + Context.Emit(OpCodes.Sub); + + if (SizeF == 0) + { + Context.EmitLdc_R4(0.5f); + } + else /* if (SizeF == 1) */ + { + Context.EmitLdc_R8(0.5); + } + + Context.Emit(OpCodes.Mul); + + if (Scalar) + { + EmitVectorZeroAll(Context, Op.Rd); + } + + EmitVectorInsertF(Context, Op.Rd, Index, SizeF); + } + public static void Fsqrt_S(AILEmitterCtx Context) { EmitScalarUnaryOpF(Context, () => @@ -335,6 +849,15 @@ namespace ChocolArm64.Instruction }); } + public static void Mla_Ve(AILEmitterCtx Context) + { + EmitVectorTernaryOpByElemZx(Context, () => + { + Context.Emit(OpCodes.Mul); + Context.Emit(OpCodes.Add); + }); + } + public static void Mls_V(AILEmitterCtx Context) { EmitVectorTernaryOpZx(Context, () => @@ -354,11 +877,26 @@ namespace ChocolArm64.Instruction EmitVectorBinaryOpByElemZx(Context, () => Context.Emit(OpCodes.Mul)); } + public static void Neg_S(AILEmitterCtx Context) + { + EmitScalarUnaryOpSx(Context, () => Context.Emit(OpCodes.Neg)); + } + public static void Neg_V(AILEmitterCtx Context) { EmitVectorUnaryOpSx(Context, () => Context.Emit(OpCodes.Neg)); } + public static void Raddhn_V(AILEmitterCtx Context) + { + EmitHighNarrow(Context, () => Context.Emit(OpCodes.Add), Round: true); + } + + public static void Rsubhn_V(AILEmitterCtx Context) + { + EmitHighNarrow(Context, () => Context.Emit(OpCodes.Sub), Round: true); + } + public static void Saddw_V(AILEmitterCtx Context) { EmitVectorWidenRmBinaryOpSx(Context, () => Context.Emit(OpCodes.Add)); @@ -406,6 +944,35 @@ namespace ChocolArm64.Instruction EmitVectorBinaryOpZx(Context, () => Context.Emit(OpCodes.Sub)); } + public static void Subhn_V(AILEmitterCtx Context) + { + EmitHighNarrow(Context, () => Context.Emit(OpCodes.Sub), Round: false); + } + + public static void Uabd_V(AILEmitterCtx Context) + { + EmitVectorBinaryOpZx(Context, () => EmitAbd(Context)); + } + + public static void Uabdl_V(AILEmitterCtx Context) + { + EmitVectorWidenRnRmBinaryOpZx(Context, () => EmitAbd(Context)); + } + + private static void EmitAbd(AILEmitterCtx Context) + { + Context.Emit(OpCodes.Sub); + + Type[] Types = new Type[] { typeof(long) }; + + Context.EmitCall(typeof(Math).GetMethod(nameof(Math.Abs), Types)); + } + + public static void Uaddl_V(AILEmitterCtx Context) + { + EmitVectorWidenRnRmBinaryOpZx(Context, () => Context.Emit(OpCodes.Add)); + } + public static void Uaddlv_V(AILEmitterCtx Context) { AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; @@ -429,9 +996,21 @@ namespace ChocolArm64.Instruction EmitVectorWidenRmBinaryOpZx(Context, () => Context.Emit(OpCodes.Add)); } + public static void Uhadd_V(AILEmitterCtx Context) + { + EmitVectorBinaryOpZx(Context, () => + { + Context.Emit(OpCodes.Add); + + Context.EmitLdc_I4(1); + + Context.Emit(OpCodes.Shr_Un); + }); + } + public static void Umull_V(AILEmitterCtx Context) { EmitVectorWidenRnRmBinaryOpZx(Context, () => Context.Emit(OpCodes.Mul)); } } -} \ No newline at end of file +} diff --git a/ChocolArm64/Instruction/AInstEmitSimdCmp.cs b/ChocolArm64/Instruction/AInstEmitSimdCmp.cs index 76861b73b..f155d7e86 100644 --- a/ChocolArm64/Instruction/AInstEmitSimdCmp.cs +++ b/ChocolArm64/Instruction/AInstEmitSimdCmp.cs @@ -110,13 +110,64 @@ namespace ChocolArm64.Instruction Fccmp_S(Context); } + public static void Fcmeq_S(AILEmitterCtx Context) + { + EmitScalarFcmp(Context, OpCodes.Beq_S); + } + + public static void Fcmeq_V(AILEmitterCtx Context) + { + EmitVectorFcmp(Context, OpCodes.Beq_S); + } + + public static void Fcmge_S(AILEmitterCtx Context) + { + EmitScalarFcmp(Context, OpCodes.Bge_S); + } + + public static void Fcmge_V(AILEmitterCtx Context) + { + EmitVectorFcmp(Context, OpCodes.Bge_S); + } + + public static void Fcmgt_S(AILEmitterCtx Context) + { + EmitScalarFcmp(Context, OpCodes.Bgt_S); + } + + public static void Fcmgt_V(AILEmitterCtx Context) + { + EmitVectorFcmp(Context, OpCodes.Bgt_S); + } + + public static void Fcmle_S(AILEmitterCtx Context) + { + EmitScalarFcmp(Context, OpCodes.Ble_S); + } + + public static void Fcmle_V(AILEmitterCtx Context) + { + EmitVectorFcmp(Context, OpCodes.Ble_S); + } + + public static void Fcmlt_S(AILEmitterCtx Context) + { + EmitScalarFcmp(Context, OpCodes.Blt_S); + } + + public static void Fcmlt_V(AILEmitterCtx Context) + { + EmitVectorFcmp(Context, OpCodes.Blt_S); + } + public static void Fcmp_S(AILEmitterCtx Context) { AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; bool CmpWithZero = !(Op is AOpCodeSimdFcond) ? Op.Bit3 : false; - //Handle NaN case. If any number is NaN, then NZCV = 0011. + //Handle NaN case. + //If any number is NaN, then NZCV = 0011. if (CmpWithZero) { EmitNaNCheck(Context, Op.Rn); @@ -140,7 +191,14 @@ namespace ChocolArm64.Instruction if (CmpWithZero) { - EmitLdcImmF(Context, 0, Op.Size); + if (Op.Size == 0) + { + Context.EmitLdc_R4(0); + } + else /* if (SizeF == 1) */ + { + Context.EmitLdc_R8(0); + } } else { @@ -190,22 +248,6 @@ namespace ChocolArm64.Instruction Fcmp_S(Context); } - private static void EmitLdcImmF(AILEmitterCtx Context, double ImmF, int Size) - { - if (Size == 0) - { - Context.EmitLdc_R4((float)ImmF); - } - else if (Size == 1) - { - Context.EmitLdc_R8(ImmF); - } - else - { - throw new ArgumentOutOfRangeException(nameof(Size)); - } - } - private static void EmitNaNCheck(AILEmitterCtx Context, int Reg) { IAOpCodeSimd Op = (IAOpCodeSimd)Context.CurrOp; @@ -268,5 +310,84 @@ namespace ChocolArm64.Instruction EmitVectorZeroUpper(Context, Op.Rd); } } + + private static void EmitScalarFcmp(AILEmitterCtx Context, OpCode ILOp) + { + EmitFcmp(Context, ILOp, 0, Scalar: true); + } + + private static void EmitVectorFcmp(AILEmitterCtx Context, OpCode ILOp) + { + AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; + + int SizeF = Op.Size & 1; + + int Bytes = Context.CurrOp.GetBitsCount() >> 3; + + for (int Index = 0; Index < Bytes >> SizeF + 2; Index++) + { + EmitFcmp(Context, ILOp, Index, Scalar: false); + } + + if (Op.RegisterSize == ARegisterSize.SIMD64) + { + EmitVectorZeroUpper(Context, Op.Rd); + } + } + + private static void EmitFcmp(AILEmitterCtx Context, OpCode ILOp, int Index, bool Scalar) + { + AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; + + int SizeF = Op.Size & 1; + + ulong SzMask = ulong.MaxValue >> (64 - (32 << SizeF)); + + EmitVectorExtractF(Context, Op.Rn, Index, SizeF); + + if (Op is AOpCodeSimdReg BinOp) + { + EmitVectorExtractF(Context, BinOp.Rm, Index, SizeF); + } + else if (SizeF == 0) + { + Context.EmitLdc_R4(0); + } + else /* if (SizeF == 1) */ + { + Context.EmitLdc_R8(0); + } + + AILLabel LblTrue = new AILLabel(); + AILLabel LblEnd = new AILLabel(); + + Context.Emit(ILOp, LblTrue); + + if (Scalar) + { + EmitVectorZeroAll(Context, Op.Rd); + } + else + { + EmitVectorInsert(Context, Op.Rd, Index, SizeF + 2, 0); + } + + Context.Emit(OpCodes.Br_S, LblEnd); + + Context.MarkLabel(LblTrue); + + if (Scalar) + { + EmitVectorInsert(Context, Op.Rd, Index, 3, (long)SzMask); + + EmitVectorZeroUpper(Context, Op.Rd); + } + else + { + EmitVectorInsert(Context, Op.Rd, Index, SizeF + 2, (long)SzMask); + } + + Context.MarkLabel(LblEnd); + } } } \ No newline at end of file diff --git a/ChocolArm64/Instruction/AInstEmitSimdHelper.cs b/ChocolArm64/Instruction/AInstEmitSimdHelper.cs index 4e45a11d6..9ef9d02f7 100644 --- a/ChocolArm64/Instruction/AInstEmitSimdHelper.cs +++ b/ChocolArm64/Instruction/AInstEmitSimdHelper.cs @@ -100,6 +100,52 @@ namespace ChocolArm64.Instruction Context.EmitCall(MthdInfo); } + public static void EmitUnarySoftFloatCall(AILEmitterCtx Context, string Name) + { + IAOpCodeSimd Op = (IAOpCodeSimd)Context.CurrOp; + + int SizeF = Op.Size & 1; + + MethodInfo MthdInfo; + + if (SizeF == 0) + { + MthdInfo = typeof(ASoftFloat).GetMethod(Name, new Type[] { typeof(float) }); + } + else /* if (SizeF == 1) */ + { + MthdInfo = typeof(ASoftFloat).GetMethod(Name, new Type[] { typeof(double) }); + } + + Context.EmitCall(MthdInfo); + } + + public static void EmitScalarBinaryOpByElemF(AILEmitterCtx Context, Action Emit) + { + AOpCodeSimdRegElemF Op = (AOpCodeSimdRegElemF)Context.CurrOp; + + EmitScalarOpByElemF(Context, Emit, Op.Index, Ternary: false); + } + + public static void EmitScalarOpByElemF(AILEmitterCtx Context, Action Emit, int Elem, bool Ternary) + { + AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; + + int SizeF = Op.Size & 1; + + if (Ternary) + { + EmitVectorExtractF(Context, Op.Rd, 0, SizeF); + } + + EmitVectorExtractF(Context, Op.Rn, 0, SizeF); + EmitVectorExtractF(Context, Op.Rm, Elem, SizeF); + + Emit(); + + EmitScalarSetF(Context, Op.Rd, SizeF); + } + public static void EmitScalarUnaryOpSx(AILEmitterCtx Context, Action Emit) { EmitScalarOp(Context, Emit, OperFlags.Rn, true); @@ -381,13 +427,16 @@ namespace ChocolArm64.Instruction } EmitVectorExtract(Context, Op.Rn, Index, Op.Size, Signed); - EmitVectorExtract(Context, Op.Rm, Index, Op.Size, Signed); + EmitVectorExtract(Context, Op.Rm, Elem, Op.Size, Signed); Emit(); - EmitVectorInsert(Context, Op.Rd, Index, Op.Size); + EmitVectorInsertTmp(Context, Index, Op.Size); } + Context.EmitLdvectmp(); + Context.EmitStvec(Op.Rd); + if (Op.RegisterSize == ARegisterSize.SIMD64) { EmitVectorZeroUpper(Context, Op.Rd); @@ -444,6 +493,9 @@ namespace ChocolArm64.Instruction { AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; + Context.EmitLdvec(Op.Rd); + Context.EmitStvectmp(); + int Elems = 8 >> Op.Size; int Part = Op.RegisterSize == ARegisterSize.SIMD128 ? Elems : 0; @@ -486,6 +538,9 @@ namespace ChocolArm64.Instruction { AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; + Context.EmitLdvec(Op.Rd); + Context.EmitStvectmp(); + int Elems = 8 >> Op.Size; int Part = Op.RegisterSize == ARegisterSize.SIMD128 ? Elems : 0; @@ -533,10 +588,7 @@ namespace ChocolArm64.Instruction public static void EmitVectorExtract(AILEmitterCtx Context, int Reg, int Index, int Size, bool Signed) { - if (Size < 0 || Size > 3) - { - throw new ArgumentOutOfRangeException(nameof(Size)); - } + ThrowIfInvalid(Index, Size); IAOpCodeSimd Op = (IAOpCodeSimd)Context.CurrOp; @@ -551,6 +603,8 @@ namespace ChocolArm64.Instruction public static void EmitVectorExtractF(AILEmitterCtx Context, int Reg, int Index, int Size) { + ThrowIfInvalidF(Index, Size); + Context.EmitLdvec(Reg); Context.EmitLdc_I4(Index); @@ -586,10 +640,7 @@ namespace ChocolArm64.Instruction public static void EmitVectorInsert(AILEmitterCtx Context, int Reg, int Index, int Size) { - if (Size < 0 || Size > 3) - { - throw new ArgumentOutOfRangeException(nameof(Size)); - } + ThrowIfInvalid(Index, Size); Context.EmitLdvec(Reg); Context.EmitLdc_I4(Index); @@ -602,10 +653,7 @@ namespace ChocolArm64.Instruction public static void EmitVectorInsertTmp(AILEmitterCtx Context, int Index, int Size) { - if (Size < 0 || Size > 3) - { - throw new ArgumentOutOfRangeException(nameof(Size)); - } + ThrowIfInvalid(Index, Size); Context.EmitLdvectmp(); Context.EmitLdc_I4(Index); @@ -618,10 +666,7 @@ namespace ChocolArm64.Instruction public static void EmitVectorInsert(AILEmitterCtx Context, int Reg, int Index, int Size, long Value) { - if (Size < 0 || Size > 3) - { - throw new ArgumentOutOfRangeException(nameof(Size)); - } + ThrowIfInvalid(Index, Size); Context.EmitLdc_I8(Value); Context.EmitLdvec(Reg); @@ -635,6 +680,8 @@ namespace ChocolArm64.Instruction public static void EmitVectorInsertF(AILEmitterCtx Context, int Reg, int Index, int Size) { + ThrowIfInvalidF(Index, Size); + Context.EmitLdvec(Reg); Context.EmitLdc_I4(Index); @@ -656,6 +703,8 @@ namespace ChocolArm64.Instruction public static void EmitVectorInsertTmpF(AILEmitterCtx Context, int Index, int Size) { + ThrowIfInvalidF(Index, Size); + Context.EmitLdvectmp(); Context.EmitLdc_I4(Index); @@ -674,5 +723,31 @@ namespace ChocolArm64.Instruction Context.EmitStvectmp(); } + + private static void ThrowIfInvalid(int Index, int Size) + { + if ((uint)Size > 3) + { + throw new ArgumentOutOfRangeException(nameof(Size)); + } + + if ((uint)Index >= 16 >> Size) + { + throw new ArgumentOutOfRangeException(nameof(Index)); + } + } + + private static void ThrowIfInvalidF(int Index, int Size) + { + if ((uint)Size > 1) + { + throw new ArgumentOutOfRangeException(nameof(Size)); + } + + if ((uint)Index >= 4 >> Size) + { + throw new ArgumentOutOfRangeException(nameof(Index)); + } + } } -} \ No newline at end of file +} diff --git a/ChocolArm64/Instruction/AInstEmitSimdLogical.cs b/ChocolArm64/Instruction/AInstEmitSimdLogical.cs index 5b71a0bbb..967c3d300 100644 --- a/ChocolArm64/Instruction/AInstEmitSimdLogical.cs +++ b/ChocolArm64/Instruction/AInstEmitSimdLogical.cs @@ -32,6 +32,51 @@ namespace ChocolArm64.Instruction }); } + public static void Bif_V(AILEmitterCtx Context) + { + EmitBitBif(Context, true); + } + + public static void Bit_V(AILEmitterCtx Context) + { + EmitBitBif(Context, false); + } + + public static void EmitBitBif(AILEmitterCtx Context, bool NotRm) + { + AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; + + int Bytes = Context.CurrOp.GetBitsCount() >> 3; + + for (int Index = 0; Index < (Bytes >> Op.Size); Index++) + { + EmitVectorExtractZx(Context, Op.Rd, Index, Op.Size); + EmitVectorExtractZx(Context, Op.Rn, Index, Op.Size); + + Context.Emit(OpCodes.Xor); + + EmitVectorExtractZx(Context, Op.Rm, Index, Op.Size); + + if (NotRm) + { + Context.Emit(OpCodes.Not); + } + + Context.Emit(OpCodes.And); + + EmitVectorExtractZx(Context, Op.Rd, Index, Op.Size); + + Context.Emit(OpCodes.Xor); + + EmitVectorInsert(Context, Op.Rd, Index, Op.Size); + } + + if (Op.RegisterSize == ARegisterSize.SIMD64) + { + EmitVectorZeroUpper(Context, Op.Rd); + } + } + public static void Bsl_V(AILEmitterCtx Context) { EmitVectorTernaryOpZx(Context, () => diff --git a/ChocolArm64/Instruction/AInstEmitSimdMove.cs b/ChocolArm64/Instruction/AInstEmitSimdMove.cs index 3f427ad8a..20268d583 100644 --- a/ChocolArm64/Instruction/AInstEmitSimdMove.cs +++ b/ChocolArm64/Instruction/AInstEmitSimdMove.cs @@ -61,6 +61,9 @@ namespace ChocolArm64.Instruction { AOpCodeSimdExt Op = (AOpCodeSimdExt)Context.CurrOp; + Context.EmitLdvec(Op.Rd); + Context.EmitStvectmp(); + int Bytes = Context.CurrOp.GetBitsCount() >> 3; int Position = Op.Imm4; @@ -75,10 +78,12 @@ namespace ChocolArm64.Instruction } EmitVectorExtractZx(Context, Reg, Position++, 0); - - EmitVectorInsert(Context, Op.Rd, Index, 0); + EmitVectorInsertTmp(Context, Index, 0); } + Context.EmitLdvectmp(); + Context.EmitStvec(Op.Rd); + if (Op.RegisterSize == ARegisterSize.SIMD64) { EmitVectorZeroUpper(Context, Op.Rd); @@ -113,7 +118,7 @@ namespace ChocolArm64.Instruction EmitVectorExtractZx(Context, Op.Rn, 0, 3); - EmitIntZeroHigherIfNeeded(Context); + EmitIntZeroUpperIfNeeded(Context); Context.EmitStintzr(Op.Rd); } @@ -124,7 +129,7 @@ namespace ChocolArm64.Instruction EmitVectorExtractZx(Context, Op.Rn, 1, 3); - EmitIntZeroHigherIfNeeded(Context); + EmitIntZeroUpperIfNeeded(Context); Context.EmitStintzr(Op.Rd); } @@ -135,7 +140,7 @@ namespace ChocolArm64.Instruction Context.EmitLdintzr(Op.Rn); - EmitIntZeroHigherIfNeeded(Context); + EmitIntZeroUpperIfNeeded(Context); EmitScalarSet(Context, Op.Rd, 3); } @@ -146,7 +151,7 @@ namespace ChocolArm64.Instruction Context.EmitLdintzr(Op.Rn); - EmitIntZeroHigherIfNeeded(Context); + EmitIntZeroUpperIfNeeded(Context); EmitVectorInsert(Context, Op.Rd, 1, 3); } @@ -251,6 +256,16 @@ namespace ChocolArm64.Instruction Context.EmitStvec(Op.Rd); } + public static void Trn1_V(AILEmitterCtx Context) + { + EmitVectorTranspose(Context, Part: 0); + } + + public static void Trn2_V(AILEmitterCtx Context) + { + EmitVectorTranspose(Context, Part: 1); + } + public static void Umov_S(AILEmitterCtx Context) { AOpCodeSimdIns Op = (AOpCodeSimdIns)Context.CurrOp; @@ -301,7 +316,7 @@ namespace ChocolArm64.Instruction EmitVectorZip(Context, Part: 1); } - private static void EmitIntZeroHigherIfNeeded(AILEmitterCtx Context) + private static void EmitIntZeroUpperIfNeeded(AILEmitterCtx Context) { if (Context.CurrOp.RegisterSize == ARegisterSize.Int32) { @@ -310,6 +325,29 @@ namespace ChocolArm64.Instruction } } + private static void EmitVectorTranspose(AILEmitterCtx Context, int Part) + { + AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; + + int Bytes = Context.CurrOp.GetBitsCount() >> 3; + + int Elems = Bytes >> Op.Size; + + for (int Index = 0; Index < Elems; Index++) + { + int Elem = (Index & ~1) + Part; + + EmitVectorExtractZx(Context, (Index & 1) == 0 ? Op.Rn : Op.Rm, Elem, Op.Size); + + EmitVectorInsert(Context, Op.Rd, Index, Op.Size); + } + + if (Op.RegisterSize == ARegisterSize.SIMD64) + { + EmitVectorZeroUpper(Context, Op.Rd); + } + } + private static void EmitVectorUnzip(AILEmitterCtx Context, int Part) { AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; @@ -322,7 +360,7 @@ namespace ChocolArm64.Instruction for (int Index = 0; Index < Elems; Index++) { int Elem = Part + ((Index & (Half - 1)) << 1); - + EmitVectorExtractZx(Context, Index < Half ? Op.Rn : Op.Rm, Elem, Op.Size); EmitVectorInsert(Context, Op.Rd, Index, Op.Size); @@ -358,4 +396,4 @@ namespace ChocolArm64.Instruction } } } -} \ No newline at end of file +} diff --git a/ChocolArm64/Instruction/ASoftFallback.cs b/ChocolArm64/Instruction/ASoftFallback.cs index f79628ad0..c08f253e9 100644 --- a/ChocolArm64/Instruction/ASoftFallback.cs +++ b/ChocolArm64/Instruction/ASoftFallback.cs @@ -20,6 +20,14 @@ namespace ChocolArm64.Instruction Context.EmitCall(typeof(ASoftFallback), MthdName); } + public static uint CountLeadingSigns32(uint Value) => (uint)CountLeadingSigns(Value, 32); + public static ulong CountLeadingSigns64(ulong Value) => (ulong)CountLeadingSigns(Value, 64); + + private static ulong CountLeadingSigns(ulong Value, int Size) + { + return CountLeadingZeros((Value >> 1) ^ Value, Size - 1); + } + public static uint CountLeadingZeros32(uint Value) => (uint)CountLeadingZeros(Value, 32); public static ulong CountLeadingZeros64(ulong Value) => (ulong)CountLeadingZeros(Value, 64); @@ -41,15 +49,15 @@ namespace ChocolArm64.Instruction private const uint Crc32RevPoly = 0xedb88320; private const uint Crc32cRevPoly = 0x82f63b78; - public static uint Crc32b(uint Crc, byte Val) => Crc32 (Crc, Crc32RevPoly, Val); - public static uint Crc32h(uint Crc, byte Val) => Crc32h(Crc, Crc32RevPoly, Val); - public static uint Crc32w(uint Crc, byte Val) => Crc32w(Crc, Crc32RevPoly, Val); - public static uint Crc32x(uint Crc, byte Val) => Crc32x(Crc, Crc32RevPoly, Val); + public static uint Crc32b(uint Crc, byte Val) => Crc32 (Crc, Crc32RevPoly, Val); + public static uint Crc32h(uint Crc, ushort Val) => Crc32h(Crc, Crc32RevPoly, Val); + public static uint Crc32w(uint Crc, uint Val) => Crc32w(Crc, Crc32RevPoly, Val); + public static uint Crc32x(uint Crc, ulong Val) => Crc32x(Crc, Crc32RevPoly, Val); - public static uint Crc32cb(uint Crc, byte Val) => Crc32 (Crc, Crc32cRevPoly, Val); - public static uint Crc32ch(uint Crc, byte Val) => Crc32h(Crc, Crc32cRevPoly, Val); - public static uint Crc32cw(uint Crc, byte Val) => Crc32w(Crc, Crc32cRevPoly, Val); - public static uint Crc32cx(uint Crc, byte Val) => Crc32x(Crc, Crc32cRevPoly, Val); + public static uint Crc32cb(uint Crc, byte Val) => Crc32 (Crc, Crc32cRevPoly, Val); + public static uint Crc32ch(uint Crc, ushort Val) => Crc32h(Crc, Crc32cRevPoly, Val); + public static uint Crc32cw(uint Crc, uint Val) => Crc32w(Crc, Crc32cRevPoly, Val); + public static uint Crc32cx(uint Crc, ulong Val) => Crc32x(Crc, Crc32cRevPoly, Val); private static uint Crc32h(uint Crc, uint Poly, ushort Val) { @@ -113,7 +121,7 @@ namespace ChocolArm64.Instruction Value = ((Value & 0xcccccccccccccccc) >> 2) | ((Value & 0x3333333333333333) << 2); Value = ((Value & 0xf0f0f0f0f0f0f0f0) >> 4) | ((Value & 0x0f0f0f0f0f0f0f0f) << 4); Value = ((Value & 0xff00ff00ff00ff00) >> 8) | ((Value & 0x00ff00ff00ff00ff) << 8); - Value = ((Value & 0xffff0000ffff0000) >> 16) | ((Value & 0x0000ffff0000ffff) << 16); + Value = ((Value & 0xffff0000ffff0000) >> 16) | ((Value & 0x0000ffff0000ffff) << 16); return (Value >> 32) | (Value << 32); } @@ -242,10 +250,86 @@ namespace ChocolArm64.Instruction public static int CountSetBits8(byte Value) { - return (Value >> 0) & 1 + (Value >> 1) & 1 + - (Value >> 2) & 1 + (Value >> 3) & 1 + - (Value >> 4) & 1 + (Value >> 5) & 1 + - (Value >> 6) & 1 + (Value >> 7); + return ((Value >> 0) & 1) + ((Value >> 1) & 1) + + ((Value >> 2) & 1) + ((Value >> 3) & 1) + + ((Value >> 4) & 1) + ((Value >> 5) & 1) + + ((Value >> 6) & 1) + (Value >> 7); + } + + public static float MaxF(float val1, float val2) + { + if (val1 == 0.0 && val2 == 0.0) + { + if (BitConverter.SingleToInt32Bits(val1) < 0 && BitConverter.SingleToInt32Bits(val2) < 0) + return -0.0f; + + return 0.0f; + } + + if (val1 > val2) + return val1; + + if (float.IsNaN(val1)) + return val1; + + return val2; + } + + public static double Max(double val1, double val2) + { + if (val1 == 0.0 && val2 == 0.0) + { + if (BitConverter.DoubleToInt64Bits(val1) < 0 && BitConverter.DoubleToInt64Bits(val2) < 0) + return -0.0; + + return 0.0; + } + + if (val1 > val2) + return val1; + + if (double.IsNaN(val1)) + return val1; + + return val2; + } + + public static float MinF(float val1, float val2) + { + if (val1 == 0.0 && val2 == 0.0) + { + if (BitConverter.SingleToInt32Bits(val1) < 0 || BitConverter.SingleToInt32Bits(val2) < 0) + return -0.0f; + + return 0.0f; + } + + if (val1 < val2) + return val1; + + if (float.IsNaN(val1)) + return val1; + + return val2; + } + + public static double Min(double val1, double val2) + { + if (val1 == 0.0 && val2 == 0.0) + { + if (BitConverter.DoubleToInt64Bits(val1) < 0 || BitConverter.DoubleToInt64Bits(val2) < 0) + return -0.0; + + return 0.0; + } + + if (val1 < val2) + return val1; + + if (double.IsNaN(val1)) + return val1; + + return val2; } public static float RoundF(float Value, int Fpcr) @@ -398,4 +482,4 @@ namespace ChocolArm64.Instruction throw new ArgumentOutOfRangeException(nameof(Size)); } } -} \ No newline at end of file +} diff --git a/ChocolArm64/Instruction/ASoftFloat.cs b/ChocolArm64/Instruction/ASoftFloat.cs new file mode 100644 index 000000000..7bee69bae --- /dev/null +++ b/ChocolArm64/Instruction/ASoftFloat.cs @@ -0,0 +1,103 @@ +using System; + +namespace ChocolArm64.Instruction +{ + static class ASoftFloat + { + static ASoftFloat() + { + InvSqrtEstimateTable = BuildInvSqrtEstimateTable(); + } + + private static readonly byte[] InvSqrtEstimateTable; + + private static byte[] BuildInvSqrtEstimateTable() + { + byte[] Table = new byte[512]; + for (ulong index = 128; index < 512; index++) + { + ulong a = index; + if (a < 256) + { + a = (a << 1) + 1; + } + else + { + a = (a | 1) << 1; + } + + ulong b = 256; + while (a * (b + 1) * (b + 1) < (1ul << 28)) + { + b++; + } + b = (b + 1) >> 1; + + Table[index] = (byte)(b & 0xFF); + } + return Table; + } + + public static float InvSqrtEstimate(float x) + { + return (float)InvSqrtEstimate((double)x); + } + + public static double InvSqrtEstimate(double x) + { + ulong x_bits = (ulong)BitConverter.DoubleToInt64Bits(x); + ulong x_sign = x_bits & 0x8000000000000000; + long x_exp = (long)((x_bits >> 52) & 0x7FF); + ulong scaled = x_bits & ((1ul << 52) - 1); + + if (x_exp == 0x7ff) + { + if (scaled == 0) + { + // Infinity -> Zero + return BitConverter.Int64BitsToDouble((long)x_sign); + } + + // NaN + return BitConverter.Int64BitsToDouble((long)(x_bits | 0x0008000000000000)); + } + + if (x_exp == 0) + { + if (scaled == 0) + { + // Zero -> Infinity + return BitConverter.Int64BitsToDouble((long)(x_sign | 0x7ff0000000000000)); + } + + // Denormal + while ((scaled & (1 << 51)) == 0) + { + scaled <<= 1; + x_exp--; + } + scaled <<= 1; + } + + if (((ulong)x_exp & 1) == 1) + { + scaled >>= 45; + scaled &= 0xFF; + scaled |= 0x80; + } + else + { + scaled >>= 44; + scaled &= 0xFF; + scaled |= 0x100; + } + + ulong result_exp = ((ulong)(3068 - x_exp) / 2) & 0x7FF; + ulong estimate = (ulong)InvSqrtEstimateTable[scaled]; + ulong fraction = estimate << 44; + + ulong result = x_sign | (result_exp << 52) | fraction; + return BitConverter.Int64BitsToDouble((long)result); + } + } +} \ No newline at end of file diff --git a/ChocolArm64/Memory/AMemoryHelper.cs b/ChocolArm64/Memory/AMemoryHelper.cs index 219aeebf9..1e3462985 100644 --- a/ChocolArm64/Memory/AMemoryHelper.cs +++ b/ChocolArm64/Memory/AMemoryHelper.cs @@ -1,4 +1,6 @@ +using System; using System.IO; +using System.Runtime.InteropServices; using System.Text; namespace ChocolArm64.Memory @@ -20,11 +22,11 @@ namespace ChocolArm64.Memory } } - public static byte[] ReadBytes(AMemory Memory, long Position, int Size) + public static byte[] ReadBytes(AMemory Memory, long Position, long Size) { byte[] Data = new byte[Size]; - for (int Offs = 0; Offs < Size; Offs++) + for (long Offs = 0; Offs < Size; Offs++) { Data[Offs] = (byte)Memory.ReadByte(Position + Offs); } @@ -40,11 +42,39 @@ namespace ChocolArm64.Memory } } - public static string ReadAsciiString(AMemory Memory, long Position, int MaxSize = -1) + public unsafe static T Read(AMemory Memory, long Position) where T : struct + { + long Size = Marshal.SizeOf(); + + if ((ulong)(Position + Size) > AMemoryMgr.AddrSize) + { + throw new ArgumentOutOfRangeException(nameof(Position)); + } + + IntPtr Ptr = new IntPtr((byte*)Memory.Ram + Position); + + return Marshal.PtrToStructure(Ptr); + } + + public unsafe static void Write(AMemory Memory, long Position, T Value) where T : struct + { + long Size = Marshal.SizeOf(); + + if ((ulong)(Position + Size) > AMemoryMgr.AddrSize) + { + throw new ArgumentOutOfRangeException(nameof(Position)); + } + + IntPtr Ptr = new IntPtr((byte*)Memory.Ram + Position); + + Marshal.StructureToPtr(Value, Ptr, false); + } + + public static string ReadAsciiString(AMemory Memory, long Position, long MaxSize = -1) { using (MemoryStream MS = new MemoryStream()) { - for (int Offs = 0; Offs < MaxSize || MaxSize == -1; Offs++) + for (long Offs = 0; Offs < MaxSize || MaxSize == -1; Offs++) { byte Value = (byte)Memory.ReadByte(Position + Offs); diff --git a/ChocolArm64/State/AThreadState.cs b/ChocolArm64/State/AThreadState.cs index 6f3f62f69..ce1278866 100644 --- a/ChocolArm64/State/AThreadState.cs +++ b/ChocolArm64/State/AThreadState.cs @@ -1,5 +1,6 @@ using ChocolArm64.Events; using System; +using System.Collections.Generic; using System.Diagnostics; namespace ChocolArm64.State @@ -56,10 +57,17 @@ namespace ChocolArm64.State public event EventHandler SvcCall; public event EventHandler Undefined; + private Stack CallStack; + private static Stopwatch TickCounter; private static double HostTickFreq; + public AThreadState() + { + CallStack = new Stack(); + } + static AThreadState() { HostTickFreq = 1.0 / Stopwatch.Frequency; @@ -83,5 +91,27 @@ namespace ChocolArm64.State { Undefined?.Invoke(this, new AInstUndefinedEventArgs(Position, RawOpCode)); } + + internal void EnterMethod(long Position) + { + CallStack.Push(Position); + } + + internal void ExitMethod() + { + CallStack.TryPop(out _); + } + + internal void JumpMethod(long Position) + { + CallStack.TryPop(out _); + + CallStack.Push(Position); + } + + public long[] GetCallStack() + { + return CallStack.ToArray(); + } } } \ No newline at end of file diff --git a/ChocolArm64/Translation/AILEmitter.cs b/ChocolArm64/Translation/AILEmitter.cs index af37a6c75..55b1751f6 100644 --- a/ChocolArm64/Translation/AILEmitter.cs +++ b/ChocolArm64/Translation/AILEmitter.cs @@ -60,11 +60,11 @@ namespace ChocolArm64.Translation public AILBlock GetILBlock(int Index) => ILBlocks[Index]; - public ATranslatedSub GetSubroutine(HashSet Callees) + public ATranslatedSub GetSubroutine() { LocalAlloc = new ALocalAlloc(ILBlocks, Root); - InitSubroutine(Callees); + InitSubroutine(); InitLocals(); foreach (AILBlock ILBlock in ILBlocks) @@ -75,7 +75,7 @@ namespace ChocolArm64.Translation return Subroutine; } - private void InitSubroutine(HashSet Callees) + private void InitSubroutine() { List Params = new List(); @@ -99,7 +99,7 @@ namespace ChocolArm64.Translation Generator = Mthd.GetILGenerator(); - Subroutine = new ATranslatedSub(Mthd, Params, Callees); + Subroutine = new ATranslatedSub(Mthd, Params); } private void InitLocals() @@ -115,7 +115,7 @@ namespace ChocolArm64.Translation Generator.EmitLdarg(Index + ParamsStart); Generator.EmitStloc(GetLocalIndex(Reg)); } - } + } private Type[] GetParamTypes(IList Params) { diff --git a/ChocolArm64/Translation/AILEmitterCtx.cs b/ChocolArm64/Translation/AILEmitterCtx.cs index 466594694..a004a9665 100644 --- a/ChocolArm64/Translation/AILEmitterCtx.cs +++ b/ChocolArm64/Translation/AILEmitterCtx.cs @@ -12,8 +12,6 @@ namespace ChocolArm64.Translation { private ATranslator Translator; - private HashSet Callees; - private Dictionary Labels; private int BlkIndex; @@ -66,13 +64,11 @@ namespace ChocolArm64.Translation this.Graph = Graph; this.Root = Root; - Callees = new HashSet(); - Labels = new Dictionary(); Emitter = new AILEmitter(Graph, Root, SubName); - ILBlock = Emitter.GetILBlock(0); + ILBlock = Emitter.GetILBlock(0); OpcIndex = -1; @@ -84,7 +80,7 @@ namespace ChocolArm64.Translation public ATranslatedSub GetSubroutine() { - return Emitter.GetSubroutine(Callees); + return Emitter.GetSubroutine(); } public bool AdvanceOpCode() @@ -123,8 +119,6 @@ namespace ChocolArm64.Translation public bool TryOptEmitSubroutineCall() { - Callees.Add(((AOpCodeBImm)CurrOp).Imm); - if (CurrBlock.Next == null) { return false; @@ -152,6 +146,8 @@ namespace ChocolArm64.Translation EmitCall(Sub.Method); + Sub.AddCaller(Root.Position); + return true; } @@ -260,18 +256,24 @@ namespace ChocolArm64.Translation case AIntType.Int64: Emit(OpCodes.Conv_I8); break; } - if (IntType == AIntType.UInt64 || - IntType == AIntType.Int64) + bool Sz64 = CurrOp.RegisterSize != ARegisterSize.Int32; + + if (Sz64 == (IntType == AIntType.UInt64 || + IntType == AIntType.Int64)) { return; } - if (CurrOp.RegisterSize != ARegisterSize.Int32) + if (Sz64) { Emit(IntType >= AIntType.Int8 ? OpCodes.Conv_I8 : OpCodes.Conv_U8); } + else + { + Emit(OpCodes.Conv_U4); + } } public void EmitLsl(int Amount) => EmitILShift(Amount, OpCodes.Shl); @@ -298,7 +300,7 @@ namespace ChocolArm64.Translation EmitLdc_I4(Amount); Emit(OpCodes.Shr_Un); - + Ldloc(Tmp2Index, AIoType.Int); EmitLdc_I4(CurrOp.GetBitsCount() - Amount); @@ -459,6 +461,21 @@ namespace ChocolArm64.Translation EmitCall(ObjType.GetMethod(MthdName)); } + public void EmitPrivateCall(Type ObjType, string MthdName) + { + if (ObjType == null) + { + throw new ArgumentNullException(nameof(ObjType)); + } + + if (MthdName == null) + { + throw new ArgumentNullException(nameof(MthdName)); + } + + EmitCall(ObjType.GetMethod(MthdName, BindingFlags.Instance | BindingFlags.NonPublic)); + } + public void EmitCall(MethodInfo MthdInfo) { if (MthdInfo == null) diff --git a/ChocolArm64/Translation/ILGeneratorEx.cs b/ChocolArm64/Translation/ILGeneratorEx.cs index abb35ec3f..612993088 100644 --- a/ChocolArm64/Translation/ILGeneratorEx.cs +++ b/ChocolArm64/Translation/ILGeneratorEx.cs @@ -3,7 +3,7 @@ using System; namespace ChocolArm64 { using System.Reflection.Emit; - + static class ILGeneratorEx { public static void EmitLdc_I4(this ILGenerator Generator,int Value) diff --git a/README.md b/README.md index 841a8258c..94bcb0e2a 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,9 @@ https://openal.org/downloads/OpenAL11CoreSDK.zip - Config File: `Ryujinx.conf` should be present in executable folder. For more informations [you can go here](CONFIG.md). + + - If you are a Windows user, you can configure your keys, the logs, install OpenAL, etc... with Ryujinx-Setting. + [Download it, right here](https://github.com/AcK77/Ryujinx-Settings) **Help** diff --git a/Ryujinx.Audio/AudioFormat.cs b/Ryujinx.Audio/AudioFormat.cs new file mode 100644 index 000000000..8250d1368 --- /dev/null +++ b/Ryujinx.Audio/AudioFormat.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Audio +{ + public enum AudioFormat + { + Invalid = 0, + PcmInt8 = 1, + PcmInt16 = 2, + PcmImt24 = 3, + PcmImt32 = 4, + PcmFloat = 5, + Adpcm = 6 + } +} \ No newline at end of file diff --git a/Ryujinx.Audio/IAalOutput.cs b/Ryujinx.Audio/IAalOutput.cs new file mode 100644 index 000000000..f9978ee4d --- /dev/null +++ b/Ryujinx.Audio/IAalOutput.cs @@ -0,0 +1,24 @@ +namespace Ryujinx.Audio +{ + public interface IAalOutput + { + int OpenTrack( + int SampleRate, + int Channels, + ReleaseCallback Callback, + out AudioFormat Format); + + void CloseTrack(int Track); + + bool ContainsBuffer(int Track, long Tag); + + long[] GetReleasedBuffers(int Track, int MaxCount); + + void AppendBuffer(int Track, long Tag, byte[] Buffer); + + void Start(int Track); + void Stop(int Track); + + PlaybackState GetState(int Track); + } +} \ No newline at end of file diff --git a/Ryujinx.Audio/OpenAL/OpenALAudioOut.cs b/Ryujinx.Audio/OpenAL/OpenALAudioOut.cs new file mode 100644 index 000000000..f574b46f3 --- /dev/null +++ b/Ryujinx.Audio/OpenAL/OpenALAudioOut.cs @@ -0,0 +1,365 @@ +using OpenTK.Audio; +using OpenTK.Audio.OpenAL; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx.Audio.OpenAL +{ + public class OpenALAudioOut : IAalOutput + { + private const int MaxTracks = 256; + + private const int MaxReleased = 32; + + private AudioContext Context; + + private class Track : IDisposable + { + public int SourceId { get; private set; } + + public int SampleRate { get; private set; } + + public ALFormat Format { get; private set; } + + private ReleaseCallback Callback; + + public PlaybackState State { get; set; } + + private bool ShouldCallReleaseCallback; + + private ConcurrentDictionary Buffers; + + private Queue QueuedTagsQueue; + + private Queue ReleasedTagsQueue; + + private int LastReleasedCount; + + private bool Disposed; + + public Track(int SampleRate, ALFormat Format, ReleaseCallback Callback) + { + this.SampleRate = SampleRate; + this.Format = Format; + this.Callback = Callback; + + State = PlaybackState.Stopped; + + SourceId = AL.GenSource(); + + Buffers = new ConcurrentDictionary(); + + QueuedTagsQueue = new Queue(); + + ReleasedTagsQueue = new Queue(); + } + + public bool ContainsBuffer(long Tag) + { + SyncQueuedTags(); + + foreach (long QueuedTag in QueuedTagsQueue) + { + if (QueuedTag == Tag) + { + return true; + } + } + + return false; + } + + public long[] GetReleasedBuffers(int MaxCount) + { + ClearReleased(); + + List Tags = new List(); + + HashSet Unique = new HashSet(); + + while (MaxCount-- > 0 && ReleasedTagsQueue.TryDequeue(out long Tag)) + { + if (Unique.Add(Tag)) + { + Tags.Add(Tag); + } + } + + return Tags.ToArray(); + } + + public int AppendBuffer(long Tag) + { + if (Disposed) + { + throw new ObjectDisposedException(nameof(Track)); + } + + int Id = AL.GenBuffer(); + + Buffers.AddOrUpdate(Tag, Id, (Key, OldId) => + { + AL.DeleteBuffer(OldId); + + return Id; + }); + + QueuedTagsQueue.Enqueue(Tag); + + return Id; + } + + public void ClearReleased() + { + SyncQueuedTags(); + + AL.GetSource(SourceId, ALGetSourcei.BuffersProcessed, out int ReleasedCount); + + CheckReleaseChanges(ReleasedCount); + + if (ReleasedCount > 0) + { + AL.SourceUnqueueBuffers(SourceId, ReleasedCount); + } + } + + public void CallReleaseCallbackIfNeeded() + { + CheckReleaseChanges(); + + if (ShouldCallReleaseCallback) + { + ShouldCallReleaseCallback = false; + + Callback(); + } + } + + private void CheckReleaseChanges() + { + AL.GetSource(SourceId, ALGetSourcei.BuffersProcessed, out int ReleasedCount); + + CheckReleaseChanges(ReleasedCount); + } + + private void CheckReleaseChanges(int NewReleasedCount) + { + if (LastReleasedCount != NewReleasedCount) + { + LastReleasedCount = NewReleasedCount; + + ShouldCallReleaseCallback = true; + } + } + + private void SyncQueuedTags() + { + AL.GetSource(SourceId, ALGetSourcei.BuffersQueued, out int QueuedCount); + AL.GetSource(SourceId, ALGetSourcei.BuffersProcessed, out int ReleasedCount); + + QueuedCount -= ReleasedCount; + + while (QueuedTagsQueue.Count > QueuedCount) + { + ReleasedTagsQueue.Enqueue(QueuedTagsQueue.Dequeue()); + } + + while (ReleasedTagsQueue.Count > MaxReleased) + { + ReleasedTagsQueue.Dequeue(); + } + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool Disposing) + { + if (Disposing && !Disposed) + { + Disposed = true; + + AL.DeleteSource(SourceId); + + foreach (int Id in Buffers.Values) + { + AL.DeleteBuffer(Id); + } + } + } + } + + private ConcurrentDictionary Tracks; + + private Thread AudioPollerThread; + + private bool KeepPolling; + + public OpenALAudioOut() + { + Context = new AudioContext(); + + Tracks = new ConcurrentDictionary(); + + KeepPolling = true; + + AudioPollerThread = new Thread(AudioPollerWork); + + AudioPollerThread.Start(); + } + + private void AudioPollerWork() + { + do + { + foreach (Track Td in Tracks.Values) + { + Td.CallReleaseCallbackIfNeeded(); + } + + Thread.Yield(); + } + while (KeepPolling); + } + + public int OpenTrack( + int SampleRate, + int Channels, + ReleaseCallback Callback, + out AudioFormat Format) + { + Format = AudioFormat.PcmInt16; + + Track Td = new Track(SampleRate, GetALFormat(Channels, Format), Callback); + + for (int Id = 0; Id < MaxTracks; Id++) + { + if (Tracks.TryAdd(Id, Td)) + { + return Id; + } + } + + return -1; + } + + private ALFormat GetALFormat(int Channels, AudioFormat Format) + { + if (Channels < 1 || Channels > 2) + { + throw new ArgumentOutOfRangeException(nameof(Channels)); + } + + if (Channels == 1) + { + switch (Format) + { + case AudioFormat.PcmInt8: return ALFormat.Mono8; + case AudioFormat.PcmInt16: return ALFormat.Mono16; + } + } + else /* if (Channels == 2) */ + { + switch (Format) + { + case AudioFormat.PcmInt8: return ALFormat.Stereo8; + case AudioFormat.PcmInt16: return ALFormat.Stereo16; + } + } + + throw new ArgumentException(nameof(Format)); + } + + public void CloseTrack(int Track) + { + if (Tracks.TryRemove(Track, out Track Td)) + { + Td.Dispose(); + } + } + + public bool ContainsBuffer(int Track, long Tag) + { + if (Tracks.TryGetValue(Track, out Track Td)) + { + return Td.ContainsBuffer(Tag); + } + + return false; + } + + public long[] GetReleasedBuffers(int Track, int MaxCount) + { + if (Tracks.TryGetValue(Track, out Track Td)) + { + return Td.GetReleasedBuffers(MaxCount); + } + + return null; + } + + public void AppendBuffer(int Track, long Tag, byte[] Buffer) + { + if (Tracks.TryGetValue(Track, out Track Td)) + { + int BufferId = Td.AppendBuffer(Tag); + + AL.BufferData(BufferId, Td.Format, Buffer, Buffer.Length, Td.SampleRate); + + AL.SourceQueueBuffer(Td.SourceId, BufferId); + + StartPlaybackIfNeeded(Td); + } + } + + public void Start(int Track) + { + if (Tracks.TryGetValue(Track, out Track Td)) + { + Td.State = PlaybackState.Playing; + + StartPlaybackIfNeeded(Td); + } + } + + private void StartPlaybackIfNeeded(Track Td) + { + AL.GetSource(Td.SourceId, ALGetSourcei.SourceState, out int StateInt); + + ALSourceState State = (ALSourceState)StateInt; + + if (State != ALSourceState.Playing && Td.State == PlaybackState.Playing) + { + Td.ClearReleased(); + + AL.SourcePlay(Td.SourceId); + } + } + + public void Stop(int Track) + { + if (Tracks.TryGetValue(Track, out Track Td)) + { + Td.State = PlaybackState.Stopped; + + AL.SourceStop(Td.SourceId); + } + } + + public PlaybackState GetState(int Track) + { + if (Tracks.TryGetValue(Track, out Track Td)) + { + return Td.State; + } + + return PlaybackState.Stopped; + } + + + } +} \ No newline at end of file diff --git a/Ryujinx.Audio/PlaybackState.cs b/Ryujinx.Audio/PlaybackState.cs new file mode 100644 index 000000000..8b53128aa --- /dev/null +++ b/Ryujinx.Audio/PlaybackState.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Audio +{ + public enum PlaybackState + { + Playing = 0, + Stopped = 1 + } +} \ No newline at end of file diff --git a/Ryujinx.Audio/ReleaseCallback.cs b/Ryujinx.Audio/ReleaseCallback.cs new file mode 100644 index 000000000..f534e02cd --- /dev/null +++ b/Ryujinx.Audio/ReleaseCallback.cs @@ -0,0 +1,4 @@ +namespace Ryujinx.Audio +{ + public delegate void ReleaseCallback(); +} \ No newline at end of file diff --git a/Ryujinx.Audio/Ryujinx.Audio.csproj b/Ryujinx.Audio/Ryujinx.Audio.csproj new file mode 100644 index 000000000..2e7c86fa0 --- /dev/null +++ b/Ryujinx.Audio/Ryujinx.Audio.csproj @@ -0,0 +1,11 @@ + + + + netcoreapp2.0 + + + + + + + diff --git a/Ryujinx.Core/Config.cs b/Ryujinx.Core/Config.cs index c9a76de34..11eb1c1df 100644 --- a/Ryujinx.Core/Config.cs +++ b/Ryujinx.Core/Config.cs @@ -9,14 +9,18 @@ namespace Ryujinx.Core { public static class Config { - public static bool LoggingEnableInfo { get; private set; } - public static bool LoggingEnableTrace { get; private set; } - public static bool LoggingEnableDebug { get; private set; } - public static bool LoggingEnableWarn { get; private set; } - public static bool LoggingEnableError { get; private set; } - public static bool LoggingEnableFatal { get; private set; } - public static bool LoggingEnableIpc { get; private set; } - public static bool LoggingEnableLogFile { get; private set; } + public static bool EnableMemoryChecks { get; private set; } + public static bool LoggingEnableInfo { get; private set; } + public static bool LoggingEnableTrace { get; private set; } + public static bool LoggingEnableDebug { get; private set; } + public static bool LoggingEnableWarn { get; private set; } + public static bool LoggingEnableError { get; private set; } + public static bool LoggingEnableFatal { get; private set; } + public static bool LoggingEnableIpc { get; private set; } + public static bool LoggingEnableStub { get; private set; } + public static bool LoggingEnableLogFile { get; private set; } + public static bool LoggingEnableFilter { get; private set; } + public static bool[] LoggingFilteredClasses { get; private set; } public static JoyCon FakeJoyCon { get; private set; } @@ -26,14 +30,33 @@ namespace Ryujinx.Core var iniPath = Path.Combine(iniFolder, "Ryujinx.conf"); IniParser Parser = new IniParser(iniPath); - LoggingEnableInfo = Convert.ToBoolean(Parser.Value("Logging_Enable_Info")); - LoggingEnableTrace = Convert.ToBoolean(Parser.Value("Logging_Enable_Trace")); - LoggingEnableDebug = Convert.ToBoolean(Parser.Value("Logging_Enable_Debug")); - LoggingEnableWarn = Convert.ToBoolean(Parser.Value("Logging_Enable_Warn")); - LoggingEnableError = Convert.ToBoolean(Parser.Value("Logging_Enable_Error")); - LoggingEnableFatal = Convert.ToBoolean(Parser.Value("Logging_Enable_Fatal")); - LoggingEnableIpc = Convert.ToBoolean(Parser.Value("Logging_Enable_Ipc")); - LoggingEnableLogFile = Convert.ToBoolean(Parser.Value("Logging_Enable_LogFile")); + EnableMemoryChecks = Convert.ToBoolean(Parser.Value("Enable_Memory_Checks")); + LoggingEnableInfo = Convert.ToBoolean(Parser.Value("Logging_Enable_Info")); + LoggingEnableTrace = Convert.ToBoolean(Parser.Value("Logging_Enable_Trace")); + LoggingEnableDebug = Convert.ToBoolean(Parser.Value("Logging_Enable_Debug")); + LoggingEnableWarn = Convert.ToBoolean(Parser.Value("Logging_Enable_Warn")); + LoggingEnableError = Convert.ToBoolean(Parser.Value("Logging_Enable_Error")); + LoggingEnableFatal = Convert.ToBoolean(Parser.Value("Logging_Enable_Fatal")); + LoggingEnableIpc = Convert.ToBoolean(Parser.Value("Logging_Enable_Ipc")); + LoggingEnableStub = Convert.ToBoolean(Parser.Value("Logging_Enable_Stub")); + LoggingEnableLogFile = Convert.ToBoolean(Parser.Value("Logging_Enable_LogFile")); + LoggingEnableFilter = Convert.ToBoolean(Parser.Value("Logging_Enable_Filter")); + LoggingFilteredClasses = new bool[(int)LogClass.Count]; + + string[] FilteredLogClasses = Parser.Value("Logging_Filtered_Classes", string.Empty).Split(','); + foreach (string LogClass in FilteredLogClasses) + { + if (!string.IsNullOrEmpty(LogClass.Trim())) + { + foreach (LogClass EnumItemName in Enum.GetValues(typeof(LogClass))) + { + if (EnumItemName.ToString().ToLower().Contains(LogClass.Trim().ToLower())) + { + LoggingFilteredClasses[(int)EnumItemName] = true; + } + } + } + } FakeJoyCon = new JoyCon { diff --git a/Ryujinx.Core/Hid/Hid.cs b/Ryujinx.Core/Hid/Hid.cs index f25a94375..be122c609 100644 --- a/Ryujinx.Core/Hid/Hid.cs +++ b/Ryujinx.Core/Hid/Hid.cs @@ -82,7 +82,7 @@ namespace Ryujinx.Core.Input (AMemory Memory, long Position) ShMem = ShMemPositions[ShMemPositions.Length - 1]; - Logging.Info($"HID shared memory successfully mapped to 0x{ShMem.Position:x16}!"); + Logging.Info(LogClass.ServiceHid, $"HID shared memory successfully mapped to 0x{ShMem.Position:x16}!"); Init(ShMem.Memory, ShMem.Position); } @@ -219,7 +219,7 @@ namespace Ryujinx.Core.Input Memory.WriteInt64Unchecked(TouchScreenOffset + 0x8, HidEntryCount); Memory.WriteInt64Unchecked(TouchScreenOffset + 0x10, CurrEntry); Memory.WriteInt64Unchecked(TouchScreenOffset + 0x18, HidEntryCount - 1); - Memory.WriteInt64Unchecked(TouchScreenOffset + 0x20, Timestamp); + Memory.WriteInt64Unchecked(TouchScreenOffset + 0x20, Timestamp); long TouchEntryOffset = TouchScreenOffset + HidTouchHeaderSize; diff --git a/Ryujinx.Core/Loaders/Executable.cs b/Ryujinx.Core/Loaders/Executable.cs index 943b8e510..39ee9618b 100644 --- a/Ryujinx.Core/Loaders/Executable.cs +++ b/Ryujinx.Core/Loaders/Executable.cs @@ -7,14 +7,16 @@ namespace Ryujinx.Core.Loaders { class Executable { - private AMemory Memory; - private List Dynamic; private Dictionary m_SymbolTable; public IReadOnlyDictionary SymbolTable => m_SymbolTable; + public string Name { get; private set; } + + private AMemory Memory; + public long ImageBase { get; private set; } public long ImageEnd { get; private set; } @@ -24,6 +26,8 @@ namespace Ryujinx.Core.Loaders m_SymbolTable = new Dictionary(); + Name = Exe.Name; + this.Memory = Memory; this.ImageBase = ImageBase; this.ImageEnd = ImageBase; diff --git a/Ryujinx.Core/Loaders/Executables/IExecutable.cs b/Ryujinx.Core/Loaders/Executables/IExecutable.cs index 09d0aab23..412058d88 100644 --- a/Ryujinx.Core/Loaders/Executables/IExecutable.cs +++ b/Ryujinx.Core/Loaders/Executables/IExecutable.cs @@ -2,6 +2,8 @@ namespace Ryujinx.Core.Loaders.Executables { public interface IExecutable { + string Name { get; } + byte[] Text { get; } byte[] RO { get; } byte[] Data { get; } diff --git a/Ryujinx.Core/Loaders/Executables/Nro.cs b/Ryujinx.Core/Loaders/Executables/Nro.cs index 9f4ef59f5..c3411d226 100644 --- a/Ryujinx.Core/Loaders/Executables/Nro.cs +++ b/Ryujinx.Core/Loaders/Executables/Nro.cs @@ -4,6 +4,8 @@ namespace Ryujinx.Core.Loaders.Executables { class Nro : IExecutable { + public string Name { get; private set; } + public byte[] Text { get; private set; } public byte[] RO { get; private set; } public byte[] Data { get; private set; } @@ -14,8 +16,10 @@ namespace Ryujinx.Core.Loaders.Executables public int DataOffset { get; private set; } public int BssSize { get; private set; } - public Nro(Stream Input) + public Nro(Stream Input, string Name) { + this.Name = Name; + BinaryReader Reader = new BinaryReader(Input); Input.Seek(4, SeekOrigin.Begin); diff --git a/Ryujinx.Core/Loaders/Executables/Nso.cs b/Ryujinx.Core/Loaders/Executables/Nso.cs index 7341ba622..bfe159d7c 100644 --- a/Ryujinx.Core/Loaders/Executables/Nso.cs +++ b/Ryujinx.Core/Loaders/Executables/Nso.cs @@ -6,6 +6,8 @@ namespace Ryujinx.Core.Loaders.Executables { class Nso : IExecutable { + public string Name { get; private set; } + public byte[] Text { get; private set; } public byte[] RO { get; private set; } public byte[] Data { get; private set; } @@ -27,8 +29,10 @@ namespace Ryujinx.Core.Loaders.Executables HasDataHash = 1 << 5 } - public Nso(Stream Input) + public Nso(Stream Input, string Name) { + this.Name = Name; + BinaryReader Reader = new BinaryReader(Input); Input.Seek(0, SeekOrigin.Begin); diff --git a/Ryujinx.Core/LogClass.cs b/Ryujinx.Core/LogClass.cs new file mode 100644 index 000000000..014b57325 --- /dev/null +++ b/Ryujinx.Core/LogClass.cs @@ -0,0 +1,38 @@ +namespace Ryujinx.Core +{ + public enum LogClass + { + Audio, + CPU, + GPU, + Kernel, + KernelIpc, + KernelScheduler, + KernelSvc, + Loader, + Service, + ServiceAcc, + ServiceAm, + ServiceApm, + ServiceAudio, + ServiceBsd, + ServiceCaps, + ServiceFriend, + ServiceFs, + ServiceHid, + ServiceLm, + ServiceNifm, + ServiceNs, + ServiceNv, + ServicePctl, + ServicePl, + ServicePrepo, + ServiceSet, + ServiceSfdnsres, + ServiceSm, + ServiceSss, + ServiceTime, + ServiceVi, + Count, + } +} diff --git a/Ryujinx.Core/Logging.cs b/Ryujinx.Core/Logging.cs index 89064ccbc..f6e2079a6 100644 --- a/Ryujinx.Core/Logging.cs +++ b/Ryujinx.Core/Logging.cs @@ -2,7 +2,9 @@ using System; using System.Diagnostics; using System.IO; +using System.Runtime.CompilerServices; using System.Text; +using System.Threading; namespace Ryujinx.Core { @@ -12,14 +14,28 @@ namespace Ryujinx.Core private const string LogFileName = "Ryujinx.log"; - private static bool EnableInfo = Config.LoggingEnableInfo; - private static bool EnableTrace = Config.LoggingEnableTrace; - private static bool EnableDebug = Config.LoggingEnableDebug; - private static bool EnableWarn = Config.LoggingEnableWarn; - private static bool EnableError = Config.LoggingEnableError; - private static bool EnableFatal = Config.LoggingEnableFatal; - private static bool EnableIpc = Config.LoggingEnableIpc; - private static bool EnableLogFile = Config.LoggingEnableLogFile; + private static bool EnableInfo = Config.LoggingEnableInfo; + private static bool EnableTrace = Config.LoggingEnableTrace; + private static bool EnableDebug = Config.LoggingEnableDebug; + private static bool EnableWarn = Config.LoggingEnableWarn; + private static bool EnableError = Config.LoggingEnableError; + private static bool EnableFatal = Config.LoggingEnableFatal; + private static bool EnableStub = Config.LoggingEnableStub; + private static bool EnableIpc = Config.LoggingEnableIpc; + private static bool EnableFilter = Config.LoggingEnableFilter; + private static bool EnableLogFile = Config.LoggingEnableLogFile; + private static bool[] FilteredLogClasses = Config.LoggingFilteredClasses; + + private enum LogLevel + { + Debug, + Error, + Fatal, + Info, + Stub, + Trace, + Warn + } static Logging() { @@ -30,14 +46,51 @@ namespace Ryujinx.Core ExecutionTime.Start(); } - public static string GetExecutionTime() - { - return ExecutionTime.ElapsedMilliseconds.ToString().PadLeft(8, '0') + "ms"; - } + public static string GetExecutionTime() => ExecutionTime.ElapsedMilliseconds.ToString().PadLeft(8, '0') + "ms"; - private static string WhoCalledMe() + private static void LogMessage(LogEntry LogEntry) { - return new StackTrace().GetFrame(2).GetMethod().Name; + if (EnableFilter) + if (!FilteredLogClasses[(int)LogEntry.LogClass]) + return; + + ConsoleColor consoleColor = ConsoleColor.White; + + switch (LogEntry.LogLevel) + { + case LogLevel.Debug: + consoleColor = ConsoleColor.Gray; + break; + case LogLevel.Error: + consoleColor = ConsoleColor.Red; + break; + case LogLevel.Fatal: + consoleColor = ConsoleColor.Magenta; + break; + case LogLevel.Info: + consoleColor = ConsoleColor.White; + break; + case LogLevel.Stub: + consoleColor = ConsoleColor.DarkYellow; + break; + case LogLevel.Trace: + consoleColor = ConsoleColor.DarkGray; + break; + case LogLevel.Warn: + consoleColor = ConsoleColor.Yellow; + break; + } + + LogEntry.ManagedThreadId = Thread.CurrentThread.ManagedThreadId; + + string Text = $"{LogEntry.ExecutionTime} | {LogEntry.ManagedThreadId} > {LogEntry.LogClass} > " + + $"{LogEntry.LogLevel.ToString()} > {LogEntry.CallingMember} > {LogEntry.Message}"; + + Console.ForegroundColor = consoleColor; + Console.WriteLine(Text.PadLeft(Text.Length + 1, ' ')); + Console.ResetColor(); + + LogFile(Text); } private static void LogFile(string Message) @@ -51,87 +104,108 @@ namespace Ryujinx.Core } } - public static void Info(string Message) + public static void Info(LogClass LogClass, string Message, [CallerMemberName] string CallingMember = "") { if (EnableInfo) { - string Text = $"{GetExecutionTime()} | INFO > {Message}"; - - Console.ForegroundColor = ConsoleColor.White; - Console.WriteLine(Text.PadLeft(Text.Length + 1, ' ')); - Console.ResetColor(); - - LogFile(Text); + LogMessage(new LogEntry + { + CallingMember = CallingMember, + LogLevel = LogLevel.Info, + LogClass = LogClass, + Message = Message, + ExecutionTime = GetExecutionTime() + }); } } - public static void Trace(string Message) + public static void Trace(LogClass LogClass, string Message, [CallerMemberName] string CallingMember = "") { if (EnableTrace) { - string Text = $"{GetExecutionTime()} | TRACE > {WhoCalledMe()} - {Message}"; - - Console.ForegroundColor = ConsoleColor.DarkGray; - Console.WriteLine(Text.PadLeft(Text.Length + 1, ' ')); - Console.ResetColor(); - - LogFile(Text); + LogMessage(new LogEntry + { + CallingMember = CallingMember, + LogLevel = LogLevel.Trace, + LogClass = LogClass, + Message = Message, + ExecutionTime = GetExecutionTime() + }); } } - public static void Debug(string Message) + public static void Stub(LogClass LogClass, string Message, [CallerMemberName] string CallingMember = "") + { + if (EnableStub) + { + LogMessage(new LogEntry + { + CallingMember = CallingMember, + LogLevel = LogLevel.Stub, + LogClass = LogClass, + Message = Message, + ExecutionTime = GetExecutionTime() + }); + } + } + + public static void Debug(LogClass LogClass,string Message, [CallerMemberName] string CallingMember = "") { if (EnableDebug) { - string Text = $"{GetExecutionTime()} | DEBUG > {WhoCalledMe()} - {Message}"; - - Console.ForegroundColor = ConsoleColor.Gray; - Console.WriteLine(Text.PadLeft(Text.Length + 1, ' ')); - Console.ResetColor(); - - LogFile(Text); + LogMessage(new LogEntry + { + CallingMember = CallingMember, + LogLevel = LogLevel.Debug, + LogClass = LogClass, + Message = Message, + ExecutionTime = GetExecutionTime() + }); } } - public static void Warn(string Message) + public static void Warn(LogClass LogClass, string Message, [CallerMemberName] string CallingMember = "") { if (EnableWarn) { - string Text = $"{GetExecutionTime()} | WARN > {WhoCalledMe()} - {Message}"; - - Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine(Text.PadLeft(Text.Length + 1, ' ')); - Console.ResetColor(); - - LogFile(Text); + LogMessage(new LogEntry + { + CallingMember = CallingMember, + LogLevel = LogLevel.Warn, + LogClass = LogClass, + Message = Message, + ExecutionTime = GetExecutionTime() + }); } } - public static void Error(string Message) + public static void Error(LogClass LogClass, string Message, [CallerMemberName] string CallingMember = "") { if (EnableError) { - string Text = $"{GetExecutionTime()} | ERROR > {WhoCalledMe()} - {Message}"; - - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine(Text.PadLeft(Text.Length + 1, ' ')); - Console.ResetColor(); - - LogFile(Text); + LogMessage(new LogEntry + { + CallingMember = CallingMember, + LogLevel = LogLevel.Error, + LogClass = LogClass, + Message = Message, + ExecutionTime = GetExecutionTime() + }); } } - public static void Fatal(string Message) + public static void Fatal(LogClass LogClass, string Message, [CallerMemberName] string CallingMember = "") { if (EnableFatal) { - string Text = $"{GetExecutionTime()} | FATAL > {WhoCalledMe()} - {Message}"; - - Console.ForegroundColor = ConsoleColor.Magenta; - Console.WriteLine(Text.PadLeft(Text.Length + 1, ' ')); - Console.ResetColor(); - - LogFile(Text); + LogMessage(new LogEntry + { + CallingMember = CallingMember, + LogLevel = LogLevel.Fatal, + LogClass = LogClass, + Message = Message, + ExecutionTime = GetExecutionTime() + }); } } @@ -160,7 +234,7 @@ namespace Ryujinx.Core int firstCharColumn = firstHexColumn + bytesPerLine * 3 // - 2 digit for the hexadecimal value and 1 space + (bytesPerLine - 1) / 8 // - 1 extra space every 8 characters from the 9th - + 2; // 2 spaces + + 2; // 2 spaces int lineLength = firstCharColumn + bytesPerLine // - characters to show the ascii value @@ -208,5 +282,15 @@ namespace Ryujinx.Core } return result.ToString(); } + + private struct LogEntry + { + public string CallingMember; + public string ExecutionTime; + public string Message; + public int ManagedThreadId; + public LogClass LogClass; + public LogLevel LogLevel; + } } } diff --git a/Ryujinx.Core/OsHle/AppletStateMgr.cs b/Ryujinx.Core/OsHle/AppletStateMgr.cs new file mode 100644 index 000000000..2199f43ee --- /dev/null +++ b/Ryujinx.Core/OsHle/AppletStateMgr.cs @@ -0,0 +1,62 @@ +using Ryujinx.Core.OsHle.Handles; +using Ryujinx.Core.OsHle.Services.Am; +using System; +using System.Collections.Concurrent; + +namespace Ryujinx.Core.OsHle +{ + class AppletStateMgr : IDisposable + { + private ConcurrentQueue Messages; + + public FocusState FocusState { get; private set; } + + public KEvent MessageEvent { get; private set; } + + public AppletStateMgr() + { + Messages = new ConcurrentQueue(); + + MessageEvent = new KEvent(); + } + + public void SetFocus(bool IsFocused) + { + FocusState = IsFocused + ? FocusState.InFocus + : FocusState.OutOfFocus; + + EnqueueMessage(MessageInfo.FocusStateChanged); + } + + public void EnqueueMessage(MessageInfo Message) + { + Messages.Enqueue(Message); + + MessageEvent.WaitEvent.Set(); + } + + public bool TryDequeueMessage(out MessageInfo Message) + { + if (Messages.Count < 2) + { + MessageEvent.WaitEvent.Reset(); + } + + return Messages.TryDequeue(out Message); + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool Disposing) + { + if (Disposing) + { + MessageEvent.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/CondVar.cs b/Ryujinx.Core/OsHle/CondVar.cs deleted file mode 100644 index f5fe3d292..000000000 --- a/Ryujinx.Core/OsHle/CondVar.cs +++ /dev/null @@ -1,142 +0,0 @@ -using Ryujinx.Core.OsHle.Handles; -using System.Collections.Generic; -using System.Threading; - -namespace Ryujinx.Core.OsHle -{ - class CondVar - { - private Process Process; - - private long CondVarAddress; - private long Timeout; - - private bool OwnsCondVarValue; - - private List WaitingThreads; - - public CondVar(Process Process, long CondVarAddress, long Timeout) - { - this.Process = Process; - this.CondVarAddress = CondVarAddress; - this.Timeout = Timeout; - - WaitingThreads = new List(); - } - - public bool WaitForSignal(HThread Thread) - { - int Count = Process.Memory.ReadInt32(CondVarAddress); - - if (Count <= 0) - { - lock (WaitingThreads) - { - WaitingThreads.Add(Thread); - } - - if (Timeout == -1) - { - Process.Scheduler.WaitForSignal(Thread); - } - else - { - bool Result = Process.Scheduler.WaitForSignal(Thread, (int)(Timeout / 1000000)); - - lock (WaitingThreads) - { - WaitingThreads.Remove(Thread); - } - - return Result; - } - } - - AcquireCondVarValue(); - - Count = Process.Memory.ReadInt32(CondVarAddress); - - if (Count > 0) - { - Process.Memory.WriteInt32(CondVarAddress, Count - 1); - } - - ReleaseCondVarValue(); - - return true; - } - - public void SetSignal(HThread Thread, int Count) - { - lock (WaitingThreads) - { - if (Count == -1) - { - Process.Scheduler.Signal(WaitingThreads.ToArray()); - - AcquireCondVarValue(); - - Process.Memory.WriteInt32(CondVarAddress, WaitingThreads.Count); - - ReleaseCondVarValue(); - - WaitingThreads.Clear(); - } - else - { - if (WaitingThreads.Count > 0) - { - int HighestPriority = WaitingThreads[0].Priority; - int HighestPrioIndex = 0; - - for (int Index = 1; Index < WaitingThreads.Count; Index++) - { - if (HighestPriority > WaitingThreads[Index].Priority) - { - HighestPriority = WaitingThreads[Index].Priority; - - HighestPrioIndex = Index; - } - } - - Process.Scheduler.Signal(WaitingThreads[HighestPrioIndex]); - - WaitingThreads.RemoveAt(HighestPrioIndex); - } - - AcquireCondVarValue(); - - Process.Memory.WriteInt32(CondVarAddress, Count); - - ReleaseCondVarValue(); - } - } - - Process.Scheduler.Suspend(Thread.ProcessorId); - Process.Scheduler.Resume(Thread); - } - - private void AcquireCondVarValue() - { - if (!OwnsCondVarValue) - { - while (!Process.Memory.AcquireAddress(CondVarAddress)) - { - Thread.Yield(); - } - - OwnsCondVarValue = true; - } - } - - private void ReleaseCondVarValue() - { - if (OwnsCondVarValue) - { - OwnsCondVarValue = false; - - Process.Memory.ReleaseAddress(CondVarAddress); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/FileDesc.cs b/Ryujinx.Core/OsHle/FileDesc.cs deleted file mode 100644 index 4be83bb07..000000000 --- a/Ryujinx.Core/OsHle/FileDesc.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Ryujinx.Core.OsHle -{ - class FileDesc - { - public string Name { get; private set; } - - public FileDesc(string Name) - { - this.Name = Name; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/GlobalStateTable.cs b/Ryujinx.Core/OsHle/GlobalStateTable.cs new file mode 100644 index 000000000..2a5714ad0 --- /dev/null +++ b/Ryujinx.Core/OsHle/GlobalStateTable.cs @@ -0,0 +1,69 @@ +using System.Collections.Concurrent; +using System.Collections.Generic; + +namespace Ryujinx.Core.OsHle +{ + class GlobalStateTable + { + private ConcurrentDictionary DictByProcess; + + public GlobalStateTable() + { + DictByProcess = new ConcurrentDictionary(); + } + + public bool Add(Process Process, int Id, object Data) + { + IdDictionary Dict = DictByProcess.GetOrAdd(Process, (Key) => new IdDictionary()); + + return Dict.Add(Id, Data); + } + + public int Add(Process Process, object Data) + { + IdDictionary Dict = DictByProcess.GetOrAdd(Process, (Key) => new IdDictionary()); + + return Dict.Add(Data); + } + + public object GetData(Process Process, int Id) + { + if (DictByProcess.TryGetValue(Process, out IdDictionary Dict)) + { + return Dict.GetData(Id); + } + + return null; + } + + public T GetData(Process Process, int Id) + { + if (DictByProcess.TryGetValue(Process, out IdDictionary Dict)) + { + return Dict.GetData(Id); + } + + return default(T); + } + + public object Delete(Process Process, int Id) + { + if (DictByProcess.TryGetValue(Process, out IdDictionary Dict)) + { + return Dict.Delete(Id); + } + + return null; + } + + public ICollection DeleteProcess(Process Process) + { + if (DictByProcess.TryRemove(Process, out IdDictionary Dict)) + { + return Dict.Clear(); + } + + return null; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Handles/HDomain.cs b/Ryujinx.Core/OsHle/Handles/HDomain.cs deleted file mode 100644 index 26c604554..000000000 --- a/Ryujinx.Core/OsHle/Handles/HDomain.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; - -namespace Ryujinx.Core.OsHle.Handles -{ - class HDomain : HSession, IDisposable - { - private IdDictionary Objects; - - public HDomain(HSession Session) : base(Session) - { - Objects = new IdDictionary(); - } - - public int Add(object Obj) - { - return Objects.Add(Obj); - } - - public bool Delete(int Id) - { - return Objects.Delete(Id); - } - - public object GetObject(int Id) - { - return Objects.GetData(Id); - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool Disposing) - { - if (Disposing) - { - foreach (object Obj in Objects) - { - if (Obj != this && Obj is IDisposable DisposableObj) - { - DisposableObj.Dispose(); - } - } - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Handles/HEvent.cs b/Ryujinx.Core/OsHle/Handles/HEvent.cs deleted file mode 100644 index 4e881ca24..000000000 --- a/Ryujinx.Core/OsHle/Handles/HEvent.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Ryujinx.Core.OsHle.Handles -{ - class HEvent - { - - } -} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Handles/HSession.cs b/Ryujinx.Core/OsHle/Handles/HSession.cs deleted file mode 100644 index f30e91f98..000000000 --- a/Ryujinx.Core/OsHle/Handles/HSession.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Ryujinx.Core.OsHle.IpcServices; - -namespace Ryujinx.Core.OsHle.Handles -{ - class HSession - { - public IIpcService Service { get; private set; } - - public bool IsInitialized { get; private set; } - - public int State { get; set; } - - public HSession(IIpcService Service) - { - this.Service = Service; - } - - public HSession(HSession Session) - { - Service = Session.Service; - IsInitialized = Session.IsInitialized; - } - - public void Initialize() - { - IsInitialized = true; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Handles/HSessionObj.cs b/Ryujinx.Core/OsHle/Handles/HSessionObj.cs deleted file mode 100644 index ed0530f74..000000000 --- a/Ryujinx.Core/OsHle/Handles/HSessionObj.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; - -namespace Ryujinx.Core.OsHle.Handles -{ - class HSessionObj : HSession, IDisposable - { - public object Obj { get; private set; } - - public HSessionObj(HSession Session, object Obj) : base(Session) - { - this.Obj = Obj; - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool Disposing) - { - if (Disposing && Obj != null) - { - if (Obj is IDisposable DisposableObj) - { - DisposableObj.Dispose(); - } - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Handles/HThread.cs b/Ryujinx.Core/OsHle/Handles/HThread.cs deleted file mode 100644 index c631cedc6..000000000 --- a/Ryujinx.Core/OsHle/Handles/HThread.cs +++ /dev/null @@ -1,21 +0,0 @@ -using ChocolArm64; - -namespace Ryujinx.Core.OsHle.Handles -{ - public class HThread - { - public AThread Thread { get; private set; } - - public int ProcessorId { get; private set; } - public int Priority { get; set; } - - public int ThreadId => Thread.ThreadId; - - public HThread(AThread Thread, int ProcessorId, int Priority) - { - this.Thread = Thread; - this.ProcessorId = ProcessorId; - this.Priority = Priority; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Handles/KEvent.cs b/Ryujinx.Core/OsHle/Handles/KEvent.cs new file mode 100644 index 000000000..96ff01f7f --- /dev/null +++ b/Ryujinx.Core/OsHle/Handles/KEvent.cs @@ -0,0 +1,4 @@ +namespace Ryujinx.Core.OsHle.Handles +{ + class KEvent : KSynchronizationObject { } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Handles/KProcessHandleTable.cs b/Ryujinx.Core/OsHle/Handles/KProcessHandleTable.cs index 1156e035f..2c8098834 100644 --- a/Ryujinx.Core/OsHle/Handles/KProcessHandleTable.cs +++ b/Ryujinx.Core/OsHle/Handles/KProcessHandleTable.cs @@ -1,8 +1,8 @@ -using System; +using System.Collections.Generic; namespace Ryujinx.Core.OsHle.Handles { - class KProcessHandleTable : IDisposable + class KProcessHandleTable { private IdDictionary Handles; @@ -21,43 +21,14 @@ namespace Ryujinx.Core.OsHle.Handles return Handles.GetData(Handle); } - public bool ReplaceData(int Id, object Data) + public object CloseHandle(int Handle) { - return Handles.ReplaceData(Id, Data); - } - - public bool CloseHandle(int Handle) - { - object Data = Handles.GetData(Handle); - - if (Data is HTransferMem TMem) - { - TMem.Memory.Manager.Reprotect( - TMem.Position, - TMem.Size, - TMem.Perm); - } - return Handles.Delete(Handle); } - public void Dispose() + public ICollection Clear() { - Dispose(true); - } - - protected virtual void Dispose(bool Disposing) - { - if (Disposing) - { - foreach (object Obj in Handles) - { - if (Obj is IDisposable DisposableObj) - { - DisposableObj.Dispose(); - } - } - } + return Handles.Clear(); } } } \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Handles/KProcessScheduler.cs b/Ryujinx.Core/OsHle/Handles/KProcessScheduler.cs index b11160781..81aa3fdd5 100644 --- a/Ryujinx.Core/OsHle/Handles/KProcessScheduler.cs +++ b/Ryujinx.Core/OsHle/Handles/KProcessScheduler.cs @@ -5,19 +5,31 @@ using System.Threading; namespace Ryujinx.Core.OsHle.Handles { - public class KProcessScheduler : IDisposable + class KProcessScheduler : IDisposable { + private const int LowestPriority = 0x40; + private class SchedulerThread : IDisposable { - public HThread Thread { get; private set; } + public KThread Thread { get; private set; } - public AutoResetEvent WaitEvent { get; private set; } + public ManualResetEvent SyncWaitEvent { get; private set; } + public AutoResetEvent SchedWaitEvent { get; private set; } - public SchedulerThread(HThread Thread) + public bool Active { get; set; } + + public int SyncTimeout { get; set; } + + public SchedulerThread(KThread Thread) { this.Thread = Thread; - WaitEvent = new AutoResetEvent(false); + SyncWaitEvent = new ManualResetEvent(true); + SchedWaitEvent = new AutoResetEvent(false); + + Active = true; + + SyncTimeout = 0; } public void Dispose() @@ -29,7 +41,8 @@ namespace Ryujinx.Core.OsHle.Handles { if (Disposing) { - WaitEvent.Dispose(); + SyncWaitEvent.Dispose(); + SchedWaitEvent.Dispose(); } } } @@ -51,7 +64,7 @@ namespace Ryujinx.Core.OsHle.Handles } } - public SchedulerThread Pop(int MinPriority = 0x40) + public SchedulerThread Pop(int MinPriority = LowestPriority) { lock (Threads) { @@ -65,9 +78,9 @@ namespace Ryujinx.Core.OsHle.Handles { SchedThread = Threads[Index]; - if (HighestPriority > SchedThread.Thread.Priority) + if (HighestPriority > SchedThread.Thread.ActualPriority) { - HighestPriority = SchedThread.Thread.Priority; + HighestPriority = SchedThread.Thread.ActualPriority; HighestPrioIndex = Index; } @@ -93,9 +106,17 @@ namespace Ryujinx.Core.OsHle.Handles return Threads.Contains(SchedThread); } } + + public bool Remove(SchedulerThread SchedThread) + { + lock (Threads) + { + return Threads.Remove(SchedThread); + } + } } - private ConcurrentDictionary AllThreads; + private ConcurrentDictionary AllThreads; private ThreadQueue[] WaitingToRun; @@ -105,7 +126,7 @@ namespace Ryujinx.Core.OsHle.Handles public KProcessScheduler() { - AllThreads = new ConcurrentDictionary(); + AllThreads = new ConcurrentDictionary(); WaitingToRun = new ThreadQueue[4]; @@ -119,7 +140,7 @@ namespace Ryujinx.Core.OsHle.Handles SchedLock = new object(); } - public void StartThread(HThread Thread) + public void StartThread(KThread Thread) { lock (SchedLock) { @@ -130,23 +151,119 @@ namespace Ryujinx.Core.OsHle.Handles return; } - if (!ActiveProcessors.Contains(Thread.ProcessorId)) + if (ActiveProcessors.Add(Thread.ProcessorId)) { - ActiveProcessors.Add(Thread.ProcessorId); - Thread.Thread.Execute(); - Logging.Debug($"{GetDbgThreadInfo(Thread)} running."); + PrintDbgThreadInfo(Thread, "running."); } else { WaitingToRun[Thread.ProcessorId].Push(SchedThread); - Logging.Debug($"{GetDbgThreadInfo(SchedThread.Thread)} waiting to run."); + PrintDbgThreadInfo(Thread, "waiting to run."); } } } + public void RemoveThread(KThread Thread) + { + PrintDbgThreadInfo(Thread, "exited."); + + lock (SchedLock) + { + if (AllThreads.TryRemove(Thread, out SchedulerThread SchedThread)) + { + WaitingToRun[Thread.ProcessorId].Remove(SchedThread); + + SchedThread.Dispose(); + } + + SchedulerThread NewThread = WaitingToRun[Thread.ProcessorId].Pop(); + + if (NewThread == null) + { + Logging.Debug(LogClass.KernelScheduler, $"Nothing to run on core {Thread.ProcessorId}!"); + + ActiveProcessors.Remove(Thread.ProcessorId); + + return; + } + + RunThread(NewThread); + } + } + + public void SetThreadActivity(KThread Thread, bool Active) + { + if (!AllThreads.TryGetValue(Thread, out SchedulerThread SchedThread)) + { + throw new InvalidOperationException(); + } + + SchedThread.Active = Active; + + UpdateSyncWaitEvent(SchedThread); + + WaitIfNeeded(SchedThread); + } + + public bool EnterWait(KThread Thread, int Timeout = -1) + { + if (!AllThreads.TryGetValue(Thread, out SchedulerThread SchedThread)) + { + throw new InvalidOperationException(); + } + + SchedThread.SyncTimeout = Timeout; + + UpdateSyncWaitEvent(SchedThread); + + return WaitIfNeeded(SchedThread); + } + + public void WakeUp(KThread Thread) + { + if (!AllThreads.TryGetValue(Thread, out SchedulerThread SchedThread)) + { + throw new InvalidOperationException(); + } + + SchedThread.SyncTimeout = 0; + + UpdateSyncWaitEvent(SchedThread); + + WaitIfNeeded(SchedThread); + } + + private void UpdateSyncWaitEvent(SchedulerThread SchedThread) + { + if (SchedThread.Active && SchedThread.SyncTimeout == 0) + { + SchedThread.SyncWaitEvent.Set(); + } + else + { + SchedThread.SyncWaitEvent.Reset(); + } + } + + private bool WaitIfNeeded(SchedulerThread SchedThread) + { + KThread Thread = SchedThread.Thread; + + if (!IsActive(SchedThread) && Thread.Thread.IsCurrentThread()) + { + Suspend(Thread.ProcessorId); + + return Resume(Thread); + } + else + { + return false; + } + } + public void Suspend(int ProcessorId) { lock (SchedLock) @@ -159,164 +276,120 @@ namespace Ryujinx.Core.OsHle.Handles } else { + Logging.Debug(LogClass.KernelScheduler, $"Nothing to run on core {ProcessorId}!"); + ActiveProcessors.Remove(ProcessorId); } } } - public void Resume(HThread CurrThread) + public void Yield(KThread Thread) { - SchedulerThread SchedThread; - - Logging.Debug($"{GetDbgThreadInfo(CurrThread)} entering ipc delay wait state."); + PrintDbgThreadInfo(Thread, "yielded execution."); lock (SchedLock) { - if (!AllThreads.TryGetValue(CurrThread, out SchedThread)) + SchedulerThread SchedThread = WaitingToRun[Thread.ProcessorId].Pop(Thread.ActualPriority); + + if (IsActive(Thread) && SchedThread == null) { - Logging.Error($"{GetDbgThreadInfo(CurrThread)} was not found on the scheduler queue!"); + PrintDbgThreadInfo(Thread, "resumed because theres nothing better to run."); return; } - } - - TryResumingExecution(SchedThread); - } - - public bool WaitForSignal(HThread Thread, int Timeout = -1) - { - SchedulerThread SchedThread; - - Logging.Debug($"{GetDbgThreadInfo(Thread)} entering signal wait state."); - - lock (SchedLock) - { - SchedThread = WaitingToRun[Thread.ProcessorId].Pop(); if (SchedThread != null) { RunThread(SchedThread); } - else - { - ActiveProcessors.Remove(Thread.ProcessorId); - } - - if (!AllThreads.TryGetValue(Thread, out SchedThread)) - { - Logging.Error($"{GetDbgThreadInfo(Thread)} was not found on the scheduler queue!"); - - return false; - } } - bool Result; - - if (Timeout >= 0) - { - Logging.Debug($"{GetDbgThreadInfo(Thread)} has wait timeout of {Timeout}ms."); - - Result = SchedThread.WaitEvent.WaitOne(Timeout); - } - else - { - Result = SchedThread.WaitEvent.WaitOne(); - } - - TryResumingExecution(SchedThread); - - return Result; + Resume(Thread); } - private void TryResumingExecution(SchedulerThread SchedThread) + public bool Resume(KThread Thread) { - HThread Thread = SchedThread.Thread; + if (!AllThreads.TryGetValue(Thread, out SchedulerThread SchedThread)) + { + throw new InvalidOperationException(); + } + + return TryResumingExecution(SchedThread); + } + + private bool TryResumingExecution(SchedulerThread SchedThread) + { + KThread Thread = SchedThread.Thread; + + if (!SchedThread.Active || SchedThread.SyncTimeout != 0) + { + PrintDbgThreadInfo(Thread, "entering inactive wait state..."); + } + + bool Result = false; + + if (SchedThread.SyncTimeout != 0) + { + Result = SchedThread.SyncWaitEvent.WaitOne(SchedThread.SyncTimeout); + + SchedThread.SyncTimeout = 0; + } lock (SchedLock) { if (ActiveProcessors.Add(Thread.ProcessorId)) { - Logging.Debug($"{GetDbgThreadInfo(Thread)} resuming execution..."); + PrintDbgThreadInfo(Thread, "resuming execution..."); - return; + return Result; } WaitingToRun[Thread.ProcessorId].Push(SchedThread); + + PrintDbgThreadInfo(Thread, "entering wait state..."); } - SchedThread.WaitEvent.WaitOne(); + SchedThread.SchedWaitEvent.WaitOne(); - Logging.Debug($"{GetDbgThreadInfo(Thread)} resuming execution..."); - } + PrintDbgThreadInfo(Thread, "resuming execution..."); - public void Yield(HThread Thread) - { - SchedulerThread SchedThread; - - Logging.Debug($"{GetDbgThreadInfo(Thread)} yielded execution."); - - lock (SchedLock) - { - SchedThread = WaitingToRun[Thread.ProcessorId].Pop(Thread.Priority); - - if (SchedThread == null) - { - Logging.Debug($"{GetDbgThreadInfo(Thread)} resumed because theres nothing better to run."); - - return; - } - - RunThread(SchedThread); - - if (!AllThreads.TryGetValue(Thread, out SchedThread)) - { - Logging.Error($"{GetDbgThreadInfo(Thread)} was not found on the scheduler queue!"); - - return; - } - - WaitingToRun[Thread.ProcessorId].Push(SchedThread); - } - - SchedThread.WaitEvent.WaitOne(); - - Logging.Debug($"{GetDbgThreadInfo(Thread)} resuming execution..."); + return Result; } private void RunThread(SchedulerThread SchedThread) { if (!SchedThread.Thread.Thread.Execute()) { - SchedThread.WaitEvent.Set(); + SchedThread.SchedWaitEvent.Set(); } else { - Logging.Debug($"{GetDbgThreadInfo(SchedThread.Thread)} running."); + PrintDbgThreadInfo(SchedThread.Thread, "running."); } } - public void Signal(params HThread[] Threads) + private bool IsActive(KThread Thread) { - lock (SchedLock) + if (!AllThreads.TryGetValue(Thread, out SchedulerThread SchedThread)) { - foreach (HThread Thread in Threads) - { - if (AllThreads.TryGetValue(Thread, out SchedulerThread SchedThread)) - { - if (!WaitingToRun[Thread.ProcessorId].HasThread(SchedThread)) - { - Logging.Debug($"{GetDbgThreadInfo(Thread)} signaled."); - - SchedThread.WaitEvent.Set(); - } - } - } + throw new InvalidOperationException(); } + + return IsActive(SchedThread); } - private string GetDbgThreadInfo(HThread Thread) + private bool IsActive(SchedulerThread SchedThread) { - return $"Thread {Thread.ThreadId} (core {Thread.ProcessorId}) prio {Thread.Priority}"; + return SchedThread.Active && SchedThread.SyncTimeout == 0; + } + + private void PrintDbgThreadInfo(KThread Thread, string Message) + { + Logging.Debug(LogClass.KernelScheduler, "(" + + "ThreadId: " + Thread.ThreadId + ", " + + "ProcessorId: " + Thread.ProcessorId + ", " + + "ActualPriority: " + Thread.ActualPriority + ", " + + "WantedPriority: " + Thread.WantedPriority + ") " + Message); } public void Dispose() diff --git a/Ryujinx.Core/OsHle/Handles/KSession.cs b/Ryujinx.Core/OsHle/Handles/KSession.cs new file mode 100644 index 000000000..de3f9efaa --- /dev/null +++ b/Ryujinx.Core/OsHle/Handles/KSession.cs @@ -0,0 +1,31 @@ +using Ryujinx.Core.OsHle.Services; +using System; + +namespace Ryujinx.Core.OsHle.Handles +{ + class KSession : IDisposable + { + public IpcService Service { get; private set; } + + public string ServiceName { get; private set; } + + public KSession(IpcService Service, string ServiceName) + { + this.Service = Service; + this.ServiceName = ServiceName; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool Disposing) + { + if (Disposing && Service is IDisposable DisposableService) + { + DisposableService.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Handles/KSynchronizationObject.cs b/Ryujinx.Core/OsHle/Handles/KSynchronizationObject.cs new file mode 100644 index 000000000..3f78b9655 --- /dev/null +++ b/Ryujinx.Core/OsHle/Handles/KSynchronizationObject.cs @@ -0,0 +1,28 @@ +using System; +using System.Threading; + +namespace Ryujinx.Core.OsHle.Handles +{ + class KSynchronizationObject : IDisposable + { + public ManualResetEvent WaitEvent { get; private set; } + + public KSynchronizationObject() + { + WaitEvent = new ManualResetEvent(false); + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool Disposing) + { + if (Disposing) + { + WaitEvent.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Handles/KThread.cs b/Ryujinx.Core/OsHle/Handles/KThread.cs new file mode 100644 index 000000000..4286984e2 --- /dev/null +++ b/Ryujinx.Core/OsHle/Handles/KThread.cs @@ -0,0 +1,113 @@ +using ChocolArm64; +using System; + +namespace Ryujinx.Core.OsHle.Handles +{ + class KThread : KSynchronizationObject + { + public AThread Thread { get; private set; } + + public KThread MutexOwner { get; set; } + + public KThread NextMutexThread { get; set; } + public KThread NextCondVarThread { get; set; } + + public long MutexAddress { get; set; } + public long CondVarAddress { get; set; } + + public int ActualPriority { get; private set; } + public int WantedPriority { get; private set; } + + public int ProcessorId { get; private set; } + + public int WaitHandle { get; set; } + + public int ThreadId => Thread.ThreadId; + + public KThread(AThread Thread, int ProcessorId, int Priority) + { + this.Thread = Thread; + this.ProcessorId = ProcessorId; + + ActualPriority = WantedPriority = Priority; + } + + public void SetPriority(int Priority) + { + WantedPriority = Priority; + + UpdatePriority(); + } + + public void UpdatePriority() + { + int OldPriority = ActualPriority; + + int CurrPriority = WantedPriority; + + if (NextMutexThread != null && CurrPriority > NextMutexThread.WantedPriority) + { + CurrPriority = NextMutexThread.WantedPriority; + } + + if (CurrPriority != OldPriority) + { + ActualPriority = CurrPriority; + + UpdateWaitList(); + + MutexOwner?.UpdatePriority(); + } + } + + private void UpdateWaitList() + { + KThread OwnerThread = MutexOwner; + + if (OwnerThread != null) + { + //The MutexOwner field should only be non null when the thread is + //waiting for the lock, and the lock belongs to another thread. + if (OwnerThread == this) + { + throw new InvalidOperationException(); + } + + lock (OwnerThread) + { + //Remove itself from the list. + KThread CurrThread = OwnerThread; + + while (CurrThread.NextMutexThread != null) + { + if (CurrThread.NextMutexThread == this) + { + CurrThread.NextMutexThread = NextMutexThread; + + break; + } + + CurrThread = CurrThread.NextMutexThread; + } + + //Re-add taking new priority into account. + CurrThread = OwnerThread; + + while (CurrThread.NextMutexThread != null) + { + if (CurrThread.NextMutexThread.ActualPriority < ActualPriority) + { + break; + } + + CurrThread = CurrThread.NextMutexThread; + } + + NextMutexThread = CurrThread.NextMutexThread; + + CurrThread.NextMutexThread = this; + } + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Homebrew.cs b/Ryujinx.Core/OsHle/Homebrew.cs index 2a717ca73..873dda025 100644 --- a/Ryujinx.Core/OsHle/Homebrew.cs +++ b/Ryujinx.Core/OsHle/Homebrew.cs @@ -51,7 +51,7 @@ namespace Ryujinx.Core.OsHle long Value0 = Memory.ReadInt64(Position + 0x08); long Value1 = Memory.ReadInt64(Position + 0x10); - FileName = AMemoryHelper.ReadAsciiString(Memory, Value0, (int)(Value1 - Value0)); + FileName = AMemoryHelper.ReadAsciiString(Memory, Value0, Value1 - Value0); break; } diff --git a/Ryujinx.Core/OsHle/Horizon.cs b/Ryujinx.Core/OsHle/Horizon.cs index c3f8cd8b0..6c625b09d 100644 --- a/Ryujinx.Core/OsHle/Horizon.cs +++ b/Ryujinx.Core/OsHle/Horizon.cs @@ -11,13 +11,14 @@ namespace Ryujinx.Core.OsHle internal const int HidSize = 0x40000; internal const int FontSize = 0x50; - internal ConcurrentDictionary Mutexes { get; private set; } - internal ConcurrentDictionary CondVars { get; private set; } + private KProcessScheduler Scheduler; private ConcurrentDictionary Processes; - internal HSharedMem HidSharedMem; - internal HSharedMem FontSharedMem; + internal HSharedMem HidSharedMem { get; private set; } + internal HSharedMem FontSharedMem { get; private set; } + + internal KEvent VsyncEvent { get; private set; } private Switch Ns; @@ -25,13 +26,14 @@ namespace Ryujinx.Core.OsHle { this.Ns = Ns; - Mutexes = new ConcurrentDictionary(); - CondVars = new ConcurrentDictionary(); + Scheduler = new KProcessScheduler(); Processes = new ConcurrentDictionary(); HidSharedMem = new HSharedMem(); FontSharedMem = new HSharedMem(); + + VsyncEvent = new KEvent(); } public void LoadCart(string ExeFsDir, string RomFsFile = null) @@ -52,11 +54,13 @@ namespace Ryujinx.Core.OsHle continue; } - Logging.Info($"Loading {Path.GetFileNameWithoutExtension(File)}..."); + Logging.Info(LogClass.Loader, $"Loading {Path.GetFileNameWithoutExtension(File)}..."); using (FileStream Input = new FileStream(File, FileMode.Open)) { - Nso Program = new Nso(Input); + string Name = Path.GetFileNameWithoutExtension(File); + + Nso Program = new Nso(Input, Name); MainProcess.LoadProgram(Program); } @@ -78,19 +82,23 @@ namespace Ryujinx.Core.OsHle { bool IsNro = Path.GetExtension(FileName).ToLower() == ".nro"; + string Name = Path.GetFileNameWithoutExtension(FileName); + Process MainProcess = MakeProcess(); using (FileStream Input = new FileStream(FileName, FileMode.Open)) { MainProcess.LoadProgram(IsNro - ? (IExecutable)new Nro(Input) - : (IExecutable)new Nso(Input)); + ? (IExecutable)new Nro(Input, Name) + : (IExecutable)new Nso(Input, Name)); } MainProcess.SetEmptyArgs(); MainProcess.Run(IsNro); } + public void SignalVsync() => VsyncEvent.WaitEvent.Set(); + private Process MakeProcess() { Process Process; @@ -104,21 +112,28 @@ namespace Ryujinx.Core.OsHle ProcessId++; } - Process = new Process(Ns, ProcessId); + Process = new Process(Ns, Scheduler, ProcessId); Processes.TryAdd(ProcessId, Process); } + InitializeProcess(Process); + return Process; } + private void InitializeProcess(Process Process) + { + Process.AppletState.SetFocus(true); + } + internal void ExitProcess(int ProcessId) { if (Processes.TryGetValue(ProcessId, out Process Process) && Process.NeedsHbAbi) { string NextNro = Homebrew.ReadHbAbiNextLoadPath(Process.Memory, Process.HbAbiDataPosition); - Logging.Info($"HbAbi NextLoadPath {NextNro}"); + Logging.Info(LogClass.Loader, $"HbAbi NextLoadPath {NextNro}"); if (NextNro == string.Empty) { @@ -131,11 +146,6 @@ namespace Ryujinx.Core.OsHle if (File.Exists(NextNro)) { - //TODO: Those dictionaries shouldn't even exist, - //the Mutex and CondVar helper classes should be static. - Mutexes.Clear(); - CondVars.Clear(); - LoadProgram(NextNro); } } @@ -171,6 +181,10 @@ namespace Ryujinx.Core.OsHle Process.StopAllThreadsAsync(); Process.Dispose(); } + + VsyncEvent.Dispose(); + + Scheduler.Dispose(); } } } diff --git a/Ryujinx.Core/OsHle/IdDictionary.cs b/Ryujinx.Core/OsHle/IdDictionary.cs index 0b9092461..2a498e7f6 100644 --- a/Ryujinx.Core/OsHle/IdDictionary.cs +++ b/Ryujinx.Core/OsHle/IdDictionary.cs @@ -1,11 +1,10 @@ using System; -using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; namespace Ryujinx.Core.OsHle { - class IdDictionary : IEnumerable + class IdDictionary { private ConcurrentDictionary Objs; @@ -16,6 +15,11 @@ namespace Ryujinx.Core.OsHle Objs = new ConcurrentDictionary(); } + public bool Add(int Id, object Data) + { + return Objs.TryAdd(Id, Data); + } + public int Add(object Data) { if (Objs.TryAdd(FreeIdHint, Data)) @@ -39,18 +43,6 @@ namespace Ryujinx.Core.OsHle throw new InvalidOperationException(); } - public bool ReplaceData(int Id, object Data) - { - if (Objs.ContainsKey(Id)) - { - Objs[Id] = Data; - - return true; - } - - return false; - } - public object GetData(int Id) { if (Objs.TryGetValue(Id, out object Data)) @@ -71,31 +63,25 @@ namespace Ryujinx.Core.OsHle return default(T); } - public bool Delete(int Id) + public object Delete(int Id) { if (Objs.TryRemove(Id, out object Obj)) { - if (Obj is IDisposable DisposableObj) - { - DisposableObj.Dispose(); - } - FreeIdHint = Id; - return true; + return Obj; } - return false; + return null; } - IEnumerator IEnumerable.GetEnumerator() + public ICollection Clear() { - return Objs.Values.GetEnumerator(); - } + ICollection Values = Objs.Values; - IEnumerator IEnumerable.GetEnumerator() - { - return Objs.Values.GetEnumerator(); + Objs.Clear(); + + return Values; } } } \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Ipc/IpcDomCmd.cs b/Ryujinx.Core/OsHle/Ipc/IpcDomCmd.cs deleted file mode 100644 index 1ef0c4082..000000000 --- a/Ryujinx.Core/OsHle/Ipc/IpcDomCmd.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.Core.OsHle.Ipc -{ - enum IpcDomCmd - { - SendMsg = 1, - DeleteObj = 2 - } -} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Ipc/IpcHandler.cs b/Ryujinx.Core/OsHle/Ipc/IpcHandler.cs index f2179a962..42322d41a 100644 --- a/Ryujinx.Core/OsHle/Ipc/IpcHandler.cs +++ b/Ryujinx.Core/OsHle/Ipc/IpcHandler.cs @@ -1,6 +1,5 @@ using ChocolArm64.Memory; using Ryujinx.Core.OsHle.Handles; -using Ryujinx.Core.OsHle.IpcServices; using System; using System.IO; @@ -8,20 +7,15 @@ namespace Ryujinx.Core.OsHle.Ipc { static class IpcHandler { - private const long SfciMagic = 'S' << 0 | 'F' << 8 | 'C' << 16 | 'I' << 24; - private const long SfcoMagic = 'S' << 0 | 'F' << 8 | 'C' << 16 | 'O' << 24; - public static void IpcCall( Switch Ns, Process Process, AMemory Memory, - HSession Session, + KSession Session, IpcMessage Request, - int ThreadId, - long CmdPtr, - int HndId) + long CmdPtr) { - IpcMessage Response = new IpcMessage(Request.IsDomain && Request.Type == IpcMessageType.Request); + IpcMessage Response = new IpcMessage(); using (MemoryStream Raw = new MemoryStream(Request.RawData)) { @@ -29,94 +23,25 @@ namespace Ryujinx.Core.OsHle.Ipc if (Request.Type == IpcMessageType.Request) { - string ServiceName = Session.Service.GetType().Name; + Response.Type = IpcMessageType.Response; - ServiceProcessRequest ProcReq = null; - - bool IgnoreNullPR = false; - - string DbgServiceName = string.Empty; - - if (Session is HDomain Dom) + using (MemoryStream ResMS = new MemoryStream()) { - if (Request.DomCmd == IpcDomCmd.SendMsg) - { - long Magic = ReqReader.ReadInt64(); - int CmdId = (int)ReqReader.ReadInt64(); + BinaryWriter ResWriter = new BinaryWriter(ResMS); - object Obj = Dom.GetObject(Request.DomObjId); + ServiceCtx Context = new ServiceCtx( + Ns, + Process, + Memory, + Session, + Request, + Response, + ReqReader, + ResWriter); - if (Obj is HDomain) - { - Session.Service.Commands.TryGetValue(CmdId, out ProcReq); + Session.Service.CallMethod(Context); - DbgServiceName = $"{ProcReq?.Method.Name ?? CmdId.ToString()}"; - } - else if (Obj != null) - { - ((IIpcService)Obj).Commands.TryGetValue(CmdId, out ProcReq); - - DbgServiceName = $"{Obj.GetType().Name} {ProcReq?.Method.Name ?? CmdId.ToString()}"; - } - } - else if (Request.DomCmd == IpcDomCmd.DeleteObj) - { - Dom.Delete(Request.DomObjId); - - Response = FillResponse(Response, 0); - - IgnoreNullPR = true; - } - } - else - { - long Magic = ReqReader.ReadInt64(); - int CmdId = (int)ReqReader.ReadInt64(); - - if (Session is HSessionObj) - { - object Obj = ((HSessionObj)Session).Obj; - - ((IIpcService)Obj).Commands.TryGetValue(CmdId, out ProcReq); - - DbgServiceName = $"{Obj.GetType().Name} {ProcReq?.Method.Name ?? CmdId.ToString()}"; - } - else - { - Session.Service.Commands.TryGetValue(CmdId, out ProcReq); - - DbgServiceName = $"{ProcReq?.Method.Name ?? CmdId.ToString()}"; - } - } - - DbgServiceName = $"Tid {ThreadId} {ServiceName} {DbgServiceName}"; - - Logging.Debug($"IpcMessage: {DbgServiceName}"); - - if (ProcReq != null) - { - using (MemoryStream ResMS = new MemoryStream()) - { - BinaryWriter ResWriter = new BinaryWriter(ResMS); - - ServiceCtx Context = new ServiceCtx( - Ns, - Process, - Memory, - Session, - Request, - Response, - ReqReader, - ResWriter); - - long Result = ProcReq(Context); - - Response = FillResponse(Response, Result, ResMS.ToArray()); - } - } - else if (!IgnoreNullPR) - { - throw new NotImplementedException(DbgServiceName); + Response.RawData = ResMS.ToArray(); } } else if (Request.Type == IpcMessageType.Control) @@ -128,11 +53,7 @@ namespace Ryujinx.Core.OsHle.Ipc { case 0: { - HDomain Dom = new HDomain(Session); - - Process.HandleTable.ReplaceData(HndId, Dom); - - Request = FillResponse(Response, 0, Dom.Add(Dom)); + Request = FillResponse(Response, 0, Session.Service.ConvertToDomain()); break; } @@ -140,12 +61,12 @@ namespace Ryujinx.Core.OsHle.Ipc case 3: { Request = FillResponse(Response, 0, 0x500); - + break; } - //TODO: Whats the difference between IpcDuplicateSession/Ex? - case 2: + //TODO: Whats the difference between IpcDuplicateSession/Ex? + case 2: case 4: { int Unknown = ReqReader.ReadInt32(); @@ -198,7 +119,7 @@ namespace Ryujinx.Core.OsHle.Ipc { BinaryWriter Writer = new BinaryWriter(MS); - Writer.Write(SfcoMagic); + Writer.Write(IpcMagic.Sfco); Writer.Write(Result); if (Data != null) diff --git a/Ryujinx.Core/OsHle/Ipc/IpcLog.cs b/Ryujinx.Core/OsHle/Ipc/IpcLog.cs index dfec7ccfd..01915d91a 100644 --- a/Ryujinx.Core/OsHle/Ipc/IpcLog.cs +++ b/Ryujinx.Core/OsHle/Ipc/IpcLog.cs @@ -125,8 +125,7 @@ namespace Ryujinx.Core.OsHle.Ipc Reader.ReadInt64(); //Padding - IpcMessage += Environment.NewLine + $" Domain:" + Environment.NewLine + - $" DomCmd: {Enum.GetName(typeof(IpcDomCmd), DomCmd)}" + Environment.NewLine + + IpcMessage += Environment.NewLine + $" Domain:" + Environment.NewLine + Environment.NewLine + $" DomObjId: {DomObjId.ToString()}" + Environment.NewLine; } diff --git a/Ryujinx.Core/OsHle/Ipc/IpcMagic.cs b/Ryujinx.Core/OsHle/Ipc/IpcMagic.cs new file mode 100644 index 000000000..e3b8c74eb --- /dev/null +++ b/Ryujinx.Core/OsHle/Ipc/IpcMagic.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Core.OsHle.Ipc +{ + abstract class IpcMagic + { + public const long Sfci = 'S' << 0 | 'F' << 8 | 'C' << 16 | 'I' << 24; + public const long Sfco = 'S' << 0 | 'F' << 8 | 'C' << 16 | 'O' << 24; + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Ipc/IpcMessage.cs b/Ryujinx.Core/OsHle/Ipc/IpcMessage.cs index ebb3dbca0..a99242465 100644 --- a/Ryujinx.Core/OsHle/Ipc/IpcMessage.cs +++ b/Ryujinx.Core/OsHle/Ipc/IpcMessage.cs @@ -17,10 +17,6 @@ namespace Ryujinx.Core.OsHle.Ipc public List ResponseObjIds { get; private set; } - public bool IsDomain { get; private set; } - public IpcDomCmd DomCmd { get; private set; } - public int DomObjId { get; private set; } - public byte[] RawData { get; set; } public IpcMessage() @@ -34,27 +30,18 @@ namespace Ryujinx.Core.OsHle.Ipc ResponseObjIds = new List(); } - public IpcMessage(bool Domain) : this() + public IpcMessage(byte[] Data, long CmdPtr) : this() { - IsDomain = Domain; - } - - public IpcMessage(byte[] Data, long CmdPtr, bool Domain) : this() - { - Logging.Ipc(Data, CmdPtr, Domain); - using (MemoryStream MS = new MemoryStream(Data)) { BinaryReader Reader = new BinaryReader(MS); - Initialize(Reader, CmdPtr, Domain); + Initialize(Reader, CmdPtr); } } - private void Initialize(BinaryReader Reader, long CmdPtr, bool Domain) + private void Initialize(BinaryReader Reader, long CmdPtr) { - IsDomain = Domain; - int Word0 = Reader.ReadInt32(); int Word1 = Reader.ReadInt32(); @@ -110,19 +97,6 @@ namespace Ryujinx.Core.OsHle.Ipc RecvListCount = 0; } - if (Domain && Type == IpcMessageType.Request) - { - int DomWord0 = Reader.ReadInt32(); - - DomCmd = (IpcDomCmd)(DomWord0 & 0xff); - - RawDataSize = (DomWord0 >> 16) & 0xffff; - - DomObjId = Reader.ReadInt32(); - - Reader.ReadInt64(); //Padding - } - RawData = Reader.ReadBytes(RawDataSize); Reader.BaseStream.Seek(RecvListPos, SeekOrigin.Begin); @@ -165,9 +139,7 @@ namespace Ryujinx.Core.OsHle.Ipc //This is the weirdest padding I've seen so far... int Pad1 = 0x10 - Pad0; - DataLength = (DataLength + Pad0 + Pad1 + (IsDomain ? 0x10 : 0)) / 4; - - DataLength += ResponseObjIds.Count; + DataLength = (DataLength + Pad0 + Pad1) / 4; Word1 = DataLength & 0x3ff; @@ -182,23 +154,11 @@ namespace Ryujinx.Core.OsHle.Ipc MS.Seek(Pad0, SeekOrigin.Current); - if (IsDomain) - { - Writer.Write(ResponseObjIds.Count); - Writer.Write(0); - Writer.Write(0L); - } - if (RawData != null) { Writer.Write(RawData); } - foreach (int Id in ResponseObjIds) - { - Writer.Write(Id); - } - Writer.Write(new byte[Pad1]); return MS.ToArray(); diff --git a/Ryujinx.Core/OsHle/Ipc/IpcPtrBuffDesc.cs b/Ryujinx.Core/OsHle/Ipc/IpcPtrBuffDesc.cs index d39f78db8..4eb15b9d7 100644 --- a/Ryujinx.Core/OsHle/Ipc/IpcPtrBuffDesc.cs +++ b/Ryujinx.Core/OsHle/Ipc/IpcPtrBuffDesc.cs @@ -6,7 +6,7 @@ namespace Ryujinx.Core.OsHle.Ipc { public long Position { get; private set; } public int Index { get; private set; } - public short Size { get; private set; } + public long Size { get; private set; } public IpcPtrBuffDesc(BinaryReader Reader) { @@ -20,7 +20,7 @@ namespace Ryujinx.Core.OsHle.Ipc Index = ((int)Word0 >> 0) & 0x03f; Index |= ((int)Word0 >> 3) & 0x1c0; - Size = (short)(Word0 >> 16); + Size = (ushort)(Word0 >> 16); } } } \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Kernel/KernelErr.cs b/Ryujinx.Core/OsHle/Kernel/KernelErr.cs new file mode 100644 index 000000000..b568405b7 --- /dev/null +++ b/Ryujinx.Core/OsHle/Kernel/KernelErr.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Core.OsHle.Kernel +{ + static class KernelErr + { + public const int InvalidAlignment = 102; + public const int InvalidAddress = 106; + public const int InvalidMemRange = 110; + public const int InvalidHandle = 114; + public const int Timeout = 117; + public const int InvalidInfo = 120; + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Kernel/NsTimeConverter.cs b/Ryujinx.Core/OsHle/Kernel/NsTimeConverter.cs new file mode 100644 index 000000000..84fb0b85e --- /dev/null +++ b/Ryujinx.Core/OsHle/Kernel/NsTimeConverter.cs @@ -0,0 +1,19 @@ +namespace Ryujinx.Core.OsHle.Kernel +{ + static class NsTimeConverter + { + public static int GetTimeMs(ulong Ns) + { + ulong Ms = Ns / 1_000_000; + + if (Ms < int.MaxValue) + { + return (int)Ms; + } + else + { + return int.MaxValue; + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Svc/SvcHandler.cs b/Ryujinx.Core/OsHle/Kernel/SvcHandler.cs similarity index 86% rename from Ryujinx.Core/OsHle/Svc/SvcHandler.cs rename to Ryujinx.Core/OsHle/Kernel/SvcHandler.cs index 3ce56a3cd..25d2767e3 100644 --- a/Ryujinx.Core/OsHle/Svc/SvcHandler.cs +++ b/Ryujinx.Core/OsHle/Kernel/SvcHandler.cs @@ -5,7 +5,7 @@ using Ryujinx.Core.OsHle.Handles; using System; using System.Collections.Generic; -namespace Ryujinx.Core.OsHle.Svc +namespace Ryujinx.Core.OsHle.Kernel { partial class SvcHandler : IDisposable { @@ -17,6 +17,8 @@ namespace Ryujinx.Core.OsHle.Svc private Process Process; private AMemory Memory; + private object CondVarLock; + private HashSet<(HSharedMem, long)> MappedSharedMems; private ulong CurrentHeapSize; @@ -40,6 +42,7 @@ namespace Ryujinx.Core.OsHle.Svc { 0x0c, SvcGetThreadPriority }, { 0x0d, SvcSetThreadPriority }, { 0x0f, SvcSetThreadCoreMask }, + { 0x10, SvcGetCurrentProcessorNumber }, { 0x12, SvcClearEvent }, { 0x13, SvcMapSharedMemory }, { 0x14, SvcUnmapSharedMemory }, @@ -58,13 +61,16 @@ namespace Ryujinx.Core.OsHle.Svc { 0x25, SvcGetThreadId }, { 0x26, SvcBreak }, { 0x27, SvcOutputDebugString }, - { 0x29, SvcGetInfo } + { 0x29, SvcGetInfo }, + { 0x32, SvcSetThreadActivity } }; this.Ns = Ns; this.Process = Process; this.Memory = Process.Memory; + CondVarLock = new object(); + MappedSharedMems = new HashSet<(HSharedMem, long)>(); } @@ -79,14 +85,16 @@ namespace Ryujinx.Core.OsHle.Svc if (SvcFuncs.TryGetValue(e.Id, out SvcFunc Func)) { - Logging.Trace($"(Thread {ThreadState.ThreadId}) {Func.Method.Name} called."); + Logging.Trace(LogClass.KernelSvc, $"(Thread {ThreadState.ThreadId}) {Func.Method.Name} called."); Func(ThreadState); - Logging.Trace($"(Thread {ThreadState.ThreadId}) {Func.Method.Name} ended."); + Logging.Trace(LogClass.KernelSvc, $"(Thread {ThreadState.ThreadId}) {Func.Method.Name} ended."); } else { + Process.PrintStackTrace(ThreadState); + throw new NotImplementedException(e.Id.ToString("x4")); } } diff --git a/Ryujinx.Core/OsHle/Svc/SvcMemory.cs b/Ryujinx.Core/OsHle/Kernel/SvcMemory.cs similarity index 89% rename from Ryujinx.Core/OsHle/Svc/SvcMemory.cs rename to Ryujinx.Core/OsHle/Kernel/SvcMemory.cs index 80f24d2bd..c8aedcff3 100644 --- a/Ryujinx.Core/OsHle/Svc/SvcMemory.cs +++ b/Ryujinx.Core/OsHle/Kernel/SvcMemory.cs @@ -4,7 +4,7 @@ using Ryujinx.Core.OsHle.Handles; using static Ryujinx.Core.OsHle.ErrorCode; -namespace Ryujinx.Core.OsHle.Svc +namespace Ryujinx.Core.OsHle.Kernel { partial class SvcHandler { @@ -57,7 +57,7 @@ namespace Ryujinx.Core.OsHle.Svc if (!IsValidPosition(Src)) { - Logging.Warn($"Tried to map Memory at invalid src address {Src:x16}!"); + Logging.Warn(LogClass.KernelSvc, $"Tried to map Memory at invalid src address {Src:x16}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange); @@ -66,7 +66,7 @@ namespace Ryujinx.Core.OsHle.Svc if (!IsValidMapPosition(Dst)) { - Logging.Warn($"Tried to map Memory at invalid dst address {Dst:x16}!"); + Logging.Warn(LogClass.KernelSvc, $"Tried to map Memory at invalid dst address {Dst:x16}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange); @@ -92,7 +92,7 @@ namespace Ryujinx.Core.OsHle.Svc if (!IsValidPosition(Src)) { - Logging.Warn($"Tried to unmap Memory at invalid src address {Src:x16}!"); + Logging.Warn(LogClass.KernelSvc, $"Tried to unmap Memory at invalid src address {Src:x16}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange); @@ -101,7 +101,7 @@ namespace Ryujinx.Core.OsHle.Svc if (!IsValidMapPosition(Dst)) { - Logging.Warn($"Tried to unmap Memory at invalid dst address {Dst:x16}!"); + Logging.Warn(LogClass.KernelSvc, $"Tried to unmap Memory at invalid dst address {Dst:x16}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange); @@ -158,7 +158,7 @@ namespace Ryujinx.Core.OsHle.Svc if (!IsValidPosition(Src)) { - Logging.Warn($"Tried to map SharedMemory at invalid address {Src:x16}!"); + Logging.Warn(LogClass.KernelSvc, $"Tried to map SharedMemory at invalid address {Src:x16}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange); @@ -196,7 +196,7 @@ namespace Ryujinx.Core.OsHle.Svc if (!IsValidPosition(Src)) { - Logging.Warn($"Tried to unmap SharedMemory at invalid address {Src:x16}!"); + Logging.Warn(LogClass.KernelSvc, $"Tried to unmap SharedMemory at invalid address {Src:x16}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange); @@ -230,7 +230,7 @@ namespace Ryujinx.Core.OsHle.Svc if (!IsValidPosition(Src)) { - Logging.Warn($"Tried to create TransferMemory at invalid address {Src:x16}!"); + Logging.Warn(LogClass.KernelSvc, $"Tried to create TransferMemory at invalid address {Src:x16}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange); @@ -244,7 +244,7 @@ namespace Ryujinx.Core.OsHle.Svc HTransferMem TMem = new HTransferMem(Memory, MapInfo.Perm, Src, Size); ulong Handle = (ulong)Process.HandleTable.OpenHandle(TMem); - + ThreadState.X0 = 0; ThreadState.X1 = Handle; } @@ -252,13 +252,13 @@ namespace Ryujinx.Core.OsHle.Svc private static bool IsValidPosition(long Position) { return Position >= MemoryRegions.AddrSpaceStart && - Position < MemoryRegions.AddrSpaceStart + MemoryRegions.AddrSpaceSize; + Position < MemoryRegions.AddrSpaceStart + MemoryRegions.AddrSpaceSize; } private static bool IsValidMapPosition(long Position) { return Position >= MemoryRegions.MapRegionAddress && - Position < MemoryRegions.MapRegionAddress + MemoryRegions.MapRegionSize; + Position < MemoryRegions.MapRegionAddress + MemoryRegions.MapRegionSize; } } } \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Svc/SvcSystem.cs b/Ryujinx.Core/OsHle/Kernel/SvcSystem.cs similarity index 56% rename from Ryujinx.Core/OsHle/Svc/SvcSystem.cs rename to Ryujinx.Core/OsHle/Kernel/SvcSystem.cs index 671a32d36..056b5059d 100644 --- a/Ryujinx.Core/OsHle/Svc/SvcSystem.cs +++ b/Ryujinx.Core/OsHle/Kernel/SvcSystem.cs @@ -3,12 +3,13 @@ using ChocolArm64.State; using Ryujinx.Core.OsHle.Exceptions; using Ryujinx.Core.OsHle.Handles; using Ryujinx.Core.OsHle.Ipc; +using Ryujinx.Core.OsHle.Services; using System; using System.Threading; using static Ryujinx.Core.OsHle.ErrorCode; -namespace Ryujinx.Core.OsHle.Svc +namespace Ryujinx.Core.OsHle.Kernel { partial class SvcHandler { @@ -34,7 +35,28 @@ namespace Ryujinx.Core.OsHle.Svc { int Handle = (int)ThreadState.X0; - Process.HandleTable.CloseHandle(Handle); + object Obj = Process.HandleTable.CloseHandle(Handle); + + if (Obj == null) + { + Logging.Warn(LogClass.KernelSvc, $"Tried to CloseHandle on invalid handle 0x{Handle:x8}!"); + + ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); + + return; + } + + if (Obj is KSession Session) + { + Session.Dispose(); + } + else if (Obj is HTransferMem TMem) + { + TMem.Memory.Manager.Reprotect( + TMem.Position, + TMem.Size, + TMem.Perm); + } ThreadState.X0 = 0; } @@ -43,25 +65,78 @@ namespace Ryujinx.Core.OsHle.Svc { int Handle = (int)ThreadState.X0; - //TODO: Implement events. + KEvent Event = Process.HandleTable.GetData(Handle); - ThreadState.X0 = 0; + if (Event != null) + { + Event.WaitEvent.Reset(); + + ThreadState.X0 = 0; + } + else + { + Logging.Warn(LogClass.KernelSvc, $"Tried to ResetSignal on invalid event handle 0x{Handle:x8}!"); + + ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); + } } private void SvcWaitSynchronization(AThreadState ThreadState) { - long HandlesPtr = (long)ThreadState.X0; - int HandlesCount = (int)ThreadState.X2; - long Timeout = (long)ThreadState.X3; + long HandlesPtr = (long)ThreadState.X1; + int HandlesCount = (int)ThreadState.X2; + ulong Timeout = ThreadState.X3; - //TODO: Implement events. + KThread CurrThread = Process.GetThread(ThreadState.Tpidr); - HThread CurrThread = Process.GetThread(ThreadState.Tpidr); + WaitHandle[] Handles = new WaitHandle[HandlesCount]; + + for (int Index = 0; Index < HandlesCount; Index++) + { + int Handle = Memory.ReadInt32(HandlesPtr + Index * 4); + + KSynchronizationObject SyncObj = Process.HandleTable.GetData(Handle); + + if (SyncObj == null) + { + Logging.Warn(LogClass.KernelSvc, $"Tried to WaitSynchronization on invalid handle 0x{Handle:x8}!"); + + ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); + + return; + } + + Handles[Index] = SyncObj.WaitEvent; + } Process.Scheduler.Suspend(CurrThread.ProcessorId); + + int HandleIndex; + + ulong Result = 0; + + if (Timeout != ulong.MaxValue) + { + HandleIndex = WaitHandle.WaitAny(Handles, NsTimeConverter.GetTimeMs(Timeout)); + + if (HandleIndex == WaitHandle.WaitTimeout) + { + Result = MakeError(ErrorModule.Kernel, KernelErr.Timeout); + } + } + else + { + HandleIndex = WaitHandle.WaitAny(Handles); + } + Process.Scheduler.Resume(CurrThread); - ThreadState.X0 = 0; + ThreadState.X0 = Result; + + if (Result == 0) + { + ThreadState.X1 = (ulong)HandleIndex; + } } private void SvcGetSystemTick(AThreadState ThreadState) @@ -78,76 +153,56 @@ namespace Ryujinx.Core.OsHle.Svc //TODO: Validate that app has perms to access the service, and that the service //actually exists, return error codes otherwise. - - HSession Session = new HSession(Process.Services.GetService(Name)); + KSession Session = new KSession(ServiceFactory.MakeService(Name), Name); ulong Handle = (ulong)Process.HandleTable.OpenHandle(Session); - + ThreadState.X0 = 0; ThreadState.X1 = Handle; } private void SvcSendSyncRequest(AThreadState ThreadState) { - SendSyncRequest(ThreadState, false); + SendSyncRequest(ThreadState, ThreadState.Tpidr, 0x100, (int)ThreadState.X0); } private void SvcSendSyncRequestWithUserBuffer(AThreadState ThreadState) { - SendSyncRequest(ThreadState, true); + SendSyncRequest( + ThreadState, + (long)ThreadState.X0, + (long)ThreadState.X1, + (int)ThreadState.X2); } - private void SendSyncRequest(AThreadState ThreadState, bool UserBuffer) + private void SendSyncRequest(AThreadState ThreadState, long CmdPtr, long Size, int Handle) { - long CmdPtr = ThreadState.Tpidr; - long Size = 0x100; - int Handle = 0; + KThread CurrThread = Process.GetThread(ThreadState.Tpidr); - if (UserBuffer) - { - CmdPtr = (long)ThreadState.X0; - Size = (long)ThreadState.X1; - Handle = (int)ThreadState.X2; - } - else - { - Handle = (int)ThreadState.X0; - } + byte[] CmdData = AMemoryHelper.ReadBytes(Memory, CmdPtr, Size); - HThread CurrThread = Process.GetThread(ThreadState.Tpidr); - - Process.Scheduler.Suspend(CurrThread.ProcessorId); - - byte[] CmdData = AMemoryHelper.ReadBytes(Memory, CmdPtr, (int)Size); - - HSession Session = Process.HandleTable.GetData(Handle); - - IpcMessage Cmd = new IpcMessage(CmdData, CmdPtr, Session is HDomain); + KSession Session = Process.HandleTable.GetData(Handle); if (Session != null) { - IpcHandler.IpcCall( - Ns, - Process, - Memory, - Session, - Cmd, - ThreadState.ThreadId, - CmdPtr, - Handle); + Process.Scheduler.Suspend(CurrThread.ProcessorId); - byte[] Response = AMemoryHelper.ReadBytes(Memory, CmdPtr, (int)Size); + IpcMessage Cmd = new IpcMessage(CmdData, CmdPtr); + + IpcHandler.IpcCall(Ns, Process, Memory, Session, Cmd, CmdPtr); + + Thread.Yield(); + + Process.Scheduler.Resume(CurrThread); ThreadState.X0 = 0; } else { - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidIpcReq); + Logging.Warn(LogClass.KernelSvc, $"Tried to SendSyncRequest on invalid session handle 0x{Handle:x8}!"); + + ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); } - - Thread.Yield(); - - Process.Scheduler.Resume(CurrThread); } private void SvcBreak(AThreadState ThreadState) @@ -156,6 +211,8 @@ namespace Ryujinx.Core.OsHle.Svc long Unknown = (long)ThreadState.X1; long Info = (long)ThreadState.X2; + Process.PrintStackTrace(ThreadState); + throw new GuestBrokeExecutionException(); } @@ -164,9 +221,9 @@ namespace Ryujinx.Core.OsHle.Svc long Position = (long)ThreadState.X0; long Size = (long)ThreadState.X1; - string Str = AMemoryHelper.ReadAsciiString(Memory, Position, (int)Size); + string Str = AMemoryHelper.ReadAsciiString(Memory, Position, Size); - Logging.Info($"SvcOutputDebugString: {Str}"); + Logging.Info(LogClass.KernelSvc, Str); ThreadState.X0 = 0; } @@ -180,7 +237,8 @@ namespace Ryujinx.Core.OsHle.Svc //Fail for info not available on older Kernel versions. if (InfoType == 18 || - InfoType == 19) + InfoType == 19 || + InfoType == 20) { ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidInfo); @@ -212,7 +270,7 @@ namespace Ryujinx.Core.OsHle.Svc case 6: ThreadState.X1 = MemoryRegions.TotalMemoryAvailable; break; - + case 7: ThreadState.X1 = MemoryRegions.TotalMemoryUsed + CurrentHeapSize; break; @@ -241,7 +299,10 @@ namespace Ryujinx.Core.OsHle.Svc ThreadState.X1 = MemoryRegions.MapRegionSize; break; - default: throw new NotImplementedException($"SvcGetInfo: {InfoType} {Handle} {InfoId}"); + default: + Process.PrintStackTrace(ThreadState); + + throw new NotImplementedException($"SvcGetInfo: {InfoType} {Handle:x8} {InfoId}"); } ThreadState.X0 = 0; diff --git a/Ryujinx.Core/OsHle/Kernel/SvcThread.cs b/Ryujinx.Core/OsHle/Kernel/SvcThread.cs new file mode 100644 index 000000000..e1300b739 --- /dev/null +++ b/Ryujinx.Core/OsHle/Kernel/SvcThread.cs @@ -0,0 +1,177 @@ +using ChocolArm64.State; +using Ryujinx.Core.OsHle.Handles; +using System.Threading; + +using static Ryujinx.Core.OsHle.ErrorCode; + +namespace Ryujinx.Core.OsHle.Kernel +{ + partial class SvcHandler + { + private void SvcCreateThread(AThreadState ThreadState) + { + long EntryPoint = (long)ThreadState.X1; + long ArgsPtr = (long)ThreadState.X2; + long StackTop = (long)ThreadState.X3; + int Priority = (int)ThreadState.X4; + int ProcessorId = (int)ThreadState.X5; + + if (ProcessorId == -2) + { + //TODO: Get this value from the NPDM file. + ProcessorId = 0; + } + + int Handle = Process.MakeThread( + EntryPoint, + StackTop, + ArgsPtr, + Priority, + ProcessorId); + + ThreadState.X0 = 0; + ThreadState.X1 = (ulong)Handle; + } + + private void SvcStartThread(AThreadState ThreadState) + { + int Handle = (int)ThreadState.X0; + + KThread CurrThread = Process.HandleTable.GetData(Handle); + + if (CurrThread != null) + { + Process.Scheduler.StartThread(CurrThread); + + Process.Scheduler.Yield(Process.GetThread(ThreadState.Tpidr)); + + ThreadState.X0 = 0; + } + else + { + Logging.Warn(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!"); + + ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); + } + } + + private void SvcExitThread(AThreadState ThreadState) + { + KThread CurrThread = Process.GetThread(ThreadState.Tpidr); + + CurrThread.Thread.StopExecution(); + } + + private void SvcSleepThread(AThreadState ThreadState) + { + ulong Ns = ThreadState.X0; + + KThread CurrThread = Process.GetThread(ThreadState.Tpidr); + + if (Ns == 0) + { + Process.Scheduler.Yield(CurrThread); + } + else + { + Process.Scheduler.Suspend(CurrThread.ProcessorId); + + Thread.Sleep(NsTimeConverter.GetTimeMs(Ns)); + + Process.Scheduler.Resume(CurrThread); + } + } + + private void SvcGetThreadPriority(AThreadState ThreadState) + { + int Handle = (int)ThreadState.X1; + + KThread CurrThread = Process.HandleTable.GetData(Handle); + + if (CurrThread != null) + { + ThreadState.X0 = 0; + ThreadState.X1 = (ulong)CurrThread.ActualPriority; + } + else + { + Logging.Warn(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!"); + + ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); + } + } + + private void SvcSetThreadPriority(AThreadState ThreadState) + { + int Handle = (int)ThreadState.X0; + int Priority = (int)ThreadState.X1; + + KThread CurrThread = Process.HandleTable.GetData(Handle); + + if (CurrThread != null) + { + CurrThread.SetPriority(Priority); + + ThreadState.X0 = 0; + } + else + { + Logging.Warn(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!"); + + ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); + } + } + + private void SvcSetThreadCoreMask(AThreadState ThreadState) + { + ThreadState.X0 = 0; + + //TODO: Error codes. + } + + private void SvcGetCurrentProcessorNumber(AThreadState ThreadState) + { + ThreadState.X0 = (ulong)Process.GetThread(ThreadState.Tpidr).ProcessorId; + } + + private void SvcGetThreadId(AThreadState ThreadState) + { + int Handle = (int)ThreadState.X1; + + KThread CurrThread = Process.HandleTable.GetData(Handle); + + if (CurrThread != null) + { + ThreadState.X0 = 0; + ThreadState.X1 = (ulong)CurrThread.ThreadId; + } + else + { + Logging.Warn(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!"); + + ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); + } + } + + private void SvcSetThreadActivity(AThreadState ThreadState) + { + int Handle = (int)ThreadState.X0; + bool Active = (int)ThreadState.X1 == 0; + + KThread Thread = Process.HandleTable.GetData(Handle); + + if (Thread != null) + { + Process.Scheduler.SetThreadActivity(Thread, Active); + + ThreadState.X0 = 0; + } + else + { + Logging.Warn(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!"); + + ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Kernel/SvcThreadSync.cs b/Ryujinx.Core/OsHle/Kernel/SvcThreadSync.cs new file mode 100644 index 000000000..6502e4c98 --- /dev/null +++ b/Ryujinx.Core/OsHle/Kernel/SvcThreadSync.cs @@ -0,0 +1,424 @@ +using ChocolArm64.State; +using Ryujinx.Core.OsHle.Handles; +using System; +using System.Threading; + +using static Ryujinx.Core.OsHle.ErrorCode; + +namespace Ryujinx.Core.OsHle.Kernel +{ + partial class SvcHandler + { + private const int MutexHasListenersMask = 0x40000000; + + private void SvcArbitrateLock(AThreadState ThreadState) + { + int OwnerThreadHandle = (int)ThreadState.X0; + long MutexAddress = (long)ThreadState.X1; + int WaitThreadHandle = (int)ThreadState.X2; + + if (IsPointingInsideKernel(MutexAddress)) + { + Logging.Warn(LogClass.KernelSvc, $"Invalid mutex address 0x{MutexAddress:x16}!"); + + ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress); + + return; + } + + if (IsWordAddressUnaligned(MutexAddress)) + { + Logging.Warn(LogClass.KernelSvc, $"Unaligned mutex address 0x{MutexAddress:x16}!"); + + ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAlignment); + + return; + } + + KThread OwnerThread = Process.HandleTable.GetData(OwnerThreadHandle); + + if (OwnerThread == null) + { + Logging.Warn(LogClass.KernelSvc, $"Invalid owner thread handle 0x{OwnerThreadHandle:x8}!"); + + ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); + + return; + } + + KThread WaitThread = Process.HandleTable.GetData(WaitThreadHandle); + + if (WaitThread == null) + { + Logging.Warn(LogClass.KernelSvc, $"Invalid requesting thread handle 0x{WaitThreadHandle:x8}!"); + + ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); + + return; + } + + KThread CurrThread = Process.GetThread(ThreadState.Tpidr); + + MutexLock(CurrThread, WaitThread, OwnerThreadHandle, WaitThreadHandle, MutexAddress); + + ThreadState.X0 = 0; + } + + private void SvcArbitrateUnlock(AThreadState ThreadState) + { + long MutexAddress = (long)ThreadState.X0; + + if (IsPointingInsideKernel(MutexAddress)) + { + Logging.Warn(LogClass.KernelSvc, $"Invalid mutex address 0x{MutexAddress:x16}!"); + + ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress); + + return; + } + + if (IsWordAddressUnaligned(MutexAddress)) + { + Logging.Warn(LogClass.KernelSvc, $"Unaligned mutex address 0x{MutexAddress:x16}!"); + + ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAlignment); + + return; + } + + if (MutexUnlock(Process.GetThread(ThreadState.Tpidr), MutexAddress)) + { + Process.Scheduler.Yield(Process.GetThread(ThreadState.Tpidr)); + } + + ThreadState.X0 = 0; + } + + private void SvcWaitProcessWideKeyAtomic(AThreadState ThreadState) + { + long MutexAddress = (long)ThreadState.X0; + long CondVarAddress = (long)ThreadState.X1; + int ThreadHandle = (int)ThreadState.X2; + ulong Timeout = ThreadState.X3; + + if (IsPointingInsideKernel(MutexAddress)) + { + Logging.Warn(LogClass.KernelSvc, $"Invalid mutex address 0x{MutexAddress:x16}!"); + + ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress); + + return; + } + + if (IsWordAddressUnaligned(MutexAddress)) + { + Logging.Warn(LogClass.KernelSvc, $"Unaligned mutex address 0x{MutexAddress:x16}!"); + + ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAlignment); + + return; + } + + KThread Thread = Process.HandleTable.GetData(ThreadHandle); + + if (Thread == null) + { + Logging.Warn(LogClass.KernelSvc, $"Invalid thread handle 0x{ThreadHandle:x8}!"); + + ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); + + return; + } + + KThread CurrThread = Process.GetThread(ThreadState.Tpidr); + + MutexUnlock(CurrThread, MutexAddress); + + if (!CondVarWait(CurrThread, ThreadHandle, MutexAddress, CondVarAddress, Timeout)) + { + ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.Timeout); + + return; + } + + Process.Scheduler.Yield(Process.GetThread(ThreadState.Tpidr)); + + ThreadState.X0 = 0; + } + + private void SvcSignalProcessWideKey(AThreadState ThreadState) + { + long CondVarAddress = (long)ThreadState.X0; + int Count = (int)ThreadState.X1; + + CondVarSignal(CondVarAddress, Count); + + ThreadState.X0 = 0; + } + + private void MutexLock( + KThread CurrThread, + KThread WaitThread, + int OwnerThreadHandle, + int WaitThreadHandle, + long MutexAddress) + { + int MutexValue = Process.Memory.ReadInt32(MutexAddress); + + if (MutexValue != (OwnerThreadHandle | MutexHasListenersMask)) + { + return; + } + + CurrThread.WaitHandle = WaitThreadHandle; + CurrThread.MutexAddress = MutexAddress; + + InsertWaitingMutexThread(OwnerThreadHandle, WaitThread); + + Process.Scheduler.EnterWait(WaitThread); + } + + private bool MutexUnlock(KThread CurrThread, long MutexAddress) + { + if (CurrThread == null) + { + Logging.Warn(LogClass.KernelSvc, $"Invalid mutex 0x{MutexAddress:x16}!"); + + return false; + } + + lock (CurrThread) + { + //This is the new thread that will not own the mutex. + //If no threads are waiting for the lock, then it should be null. + KThread OwnerThread = CurrThread.NextMutexThread; + + while (OwnerThread != null && OwnerThread.MutexAddress != MutexAddress) + { + OwnerThread = OwnerThread.NextMutexThread; + } + + CurrThread.NextMutexThread = null; + + if (OwnerThread != null) + { + int HasListeners = OwnerThread.NextMutexThread != null ? MutexHasListenersMask : 0; + + Process.Memory.WriteInt32(MutexAddress, HasListeners | OwnerThread.WaitHandle); + + OwnerThread.WaitHandle = 0; + OwnerThread.MutexAddress = 0; + OwnerThread.CondVarAddress = 0; + + OwnerThread.MutexOwner = null; + + OwnerThread.UpdatePriority(); + + Process.Scheduler.WakeUp(OwnerThread); + + return true; + } + else + { + Process.Memory.WriteInt32(MutexAddress, 0); + + return false; + } + } + } + + private bool CondVarWait( + KThread WaitThread, + int WaitThreadHandle, + long MutexAddress, + long CondVarAddress, + ulong Timeout) + { + WaitThread.WaitHandle = WaitThreadHandle; + WaitThread.MutexAddress = MutexAddress; + WaitThread.CondVarAddress = CondVarAddress; + + lock (CondVarLock) + { + KThread CurrThread = Process.ThreadArbiterList; + + if (CurrThread != null) + { + bool DoInsert = CurrThread != WaitThread; + + while (CurrThread.NextCondVarThread != null) + { + if (CurrThread.NextCondVarThread.ActualPriority < WaitThread.ActualPriority) + { + break; + } + + CurrThread = CurrThread.NextCondVarThread; + + DoInsert &= CurrThread != WaitThread; + } + + //Only insert if the node doesn't already exist in the list. + //This prevents circular references. + if (DoInsert) + { + if (WaitThread.NextCondVarThread != null) + { + throw new InvalidOperationException(); + } + + WaitThread.NextCondVarThread = CurrThread.NextCondVarThread; + CurrThread.NextCondVarThread = WaitThread; + } + } + else + { + Process.ThreadArbiterList = WaitThread; + } + } + + if (Timeout != ulong.MaxValue) + { + return Process.Scheduler.EnterWait(WaitThread, NsTimeConverter.GetTimeMs(Timeout)); + } + else + { + return Process.Scheduler.EnterWait(WaitThread); + } + } + + private void CondVarSignal(long CondVarAddress, int Count) + { + lock (CondVarLock) + { + KThread PrevThread = null; + KThread CurrThread = Process.ThreadArbiterList; + + while (CurrThread != null && (Count == -1 || Count > 0)) + { + if (CurrThread.CondVarAddress == CondVarAddress) + { + if (PrevThread != null) + { + PrevThread.NextCondVarThread = CurrThread.NextCondVarThread; + } + else + { + Process.ThreadArbiterList = CurrThread.NextCondVarThread; + } + + CurrThread.NextCondVarThread = null; + + AcquireMutexValue(CurrThread.MutexAddress); + + int MutexValue = Process.Memory.ReadInt32(CurrThread.MutexAddress); + + MutexValue &= ~MutexHasListenersMask; + + if (MutexValue == 0) + { + //Give the lock to this thread. + Process.Memory.WriteInt32(CurrThread.MutexAddress, CurrThread.WaitHandle); + + CurrThread.WaitHandle = 0; + CurrThread.MutexAddress = 0; + CurrThread.CondVarAddress = 0; + + CurrThread.MutexOwner = null; + + CurrThread.UpdatePriority(); + + Process.Scheduler.WakeUp(CurrThread); + } + else + { + //Wait until the lock is released. + InsertWaitingMutexThread(MutexValue, CurrThread); + + MutexValue |= MutexHasListenersMask; + + Process.Memory.WriteInt32(CurrThread.MutexAddress, MutexValue); + } + + ReleaseMutexValue(CurrThread.MutexAddress); + + Count--; + } + + PrevThread = CurrThread; + CurrThread = CurrThread.NextCondVarThread; + } + } + } + + private void InsertWaitingMutexThread(int OwnerThreadHandle, KThread WaitThread) + { + KThread OwnerThread = Process.HandleTable.GetData(OwnerThreadHandle); + + if (OwnerThread == null) + { + Logging.Warn(LogClass.KernelSvc, $"Invalid thread handle 0x{OwnerThreadHandle:x8}!"); + + return; + } + + WaitThread.MutexOwner = OwnerThread; + + lock (OwnerThread) + { + KThread CurrThread = OwnerThread; + + while (CurrThread.NextMutexThread != null) + { + if (CurrThread == WaitThread) + { + return; + } + + if (CurrThread.NextMutexThread.ActualPriority < WaitThread.ActualPriority) + { + break; + } + + CurrThread = CurrThread.NextMutexThread; + } + + if (CurrThread != WaitThread) + { + if (WaitThread.NextCondVarThread != null) + { + throw new InvalidOperationException(); + } + + WaitThread.NextMutexThread = CurrThread.NextMutexThread; + CurrThread.NextMutexThread = WaitThread; + } + } + + OwnerThread.UpdatePriority(); + } + + private void AcquireMutexValue(long MutexAddress) + { + while (!Process.Memory.AcquireAddress(MutexAddress)) + { + Thread.Yield(); + } + } + + private void ReleaseMutexValue(long MutexAddress) + { + Process.Memory.ReleaseAddress(MutexAddress); + } + + private bool IsPointingInsideKernel(long Address) + { + return ((ulong)Address + 0x1000000000) < 0xffffff000; + } + + private bool IsWordAddressUnaligned(long Address) + { + return (Address & 3) != 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/KernelErr.cs b/Ryujinx.Core/OsHle/KernelErr.cs deleted file mode 100644 index 19983af19..000000000 --- a/Ryujinx.Core/OsHle/KernelErr.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Ryujinx.Core.OsHle -{ - static class KernelErr - { - public const int InvalidMemRange = 110; - public const int InvalidHandle = 114; - public const int Timeout = 117; - public const int InvalidInfo = 120; - public const int InvalidIpcReq = 123; - } -} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/MemoryRegions.cs b/Ryujinx.Core/OsHle/MemoryRegions.cs index 75b97b1f2..f7ef47f46 100644 --- a/Ryujinx.Core/OsHle/MemoryRegions.cs +++ b/Ryujinx.Core/OsHle/MemoryRegions.cs @@ -16,7 +16,7 @@ namespace Ryujinx.Core.OsHle public const long MainStackAddress = AMemoryMgr.AddrSize - MainStackSize; - public const long TlsPagesSize = 0x4000; + public const long TlsPagesSize = 0x20000; public const long TlsPagesAddress = MainStackAddress - TlsPagesSize; diff --git a/Ryujinx.Core/OsHle/Mutex.cs b/Ryujinx.Core/OsHle/Mutex.cs deleted file mode 100644 index c619e1217..000000000 --- a/Ryujinx.Core/OsHle/Mutex.cs +++ /dev/null @@ -1,122 +0,0 @@ -using Ryujinx.Core.OsHle.Handles; -using System.Collections.Concurrent; -using System.Threading; - -namespace Ryujinx.Core.OsHle -{ - class Mutex - { - private const int MutexHasListenersMask = 0x40000000; - - private Process Process; - - private long MutexAddress; - - private bool OwnsMutexValue; - - private object EnterWaitLock; - - private ConcurrentQueue WaitingThreads; - - public Mutex(Process Process, long MutexAddress, int OwnerThreadHandle) - { - this.Process = Process; - this.MutexAddress = MutexAddress; - - //Process.Memory.WriteInt32(MutexAddress, OwnerThreadHandle); - - EnterWaitLock = new object(); - - WaitingThreads = new ConcurrentQueue(); - } - - public void WaitForLock(HThread RequestingThread, int RequestingThreadHandle) - { - AcquireMutexValue(); - - lock (EnterWaitLock) - { - int CurrentThreadHandle = Process.Memory.ReadInt32(MutexAddress) & ~MutexHasListenersMask; - - if (CurrentThreadHandle == RequestingThreadHandle || - CurrentThreadHandle == 0) - { - return; - } - - Process.Memory.WriteInt32(MutexAddress, CurrentThreadHandle | MutexHasListenersMask); - - ReleaseMutexValue(); - - WaitingThreads.Enqueue(RequestingThread); - } - - Process.Scheduler.WaitForSignal(RequestingThread); - } - - public void GiveUpLock(int ThreadHandle) - { - AcquireMutexValue(); - - lock (EnterWaitLock) - { - int CurrentThread = Process.Memory.ReadInt32(MutexAddress) & ~MutexHasListenersMask; - - if (CurrentThread == ThreadHandle) - { - Unlock(); - } - } - - ReleaseMutexValue(); - } - - public void Unlock() - { - AcquireMutexValue(); - - lock (EnterWaitLock) - { - int HasListeners = WaitingThreads.Count > 1 ? MutexHasListenersMask : 0; - - Process.Memory.WriteInt32(MutexAddress, HasListeners); - - ReleaseMutexValue(); - - HThread[] UnlockedThreads = new HThread[WaitingThreads.Count]; - - int Index = 0; - - while (WaitingThreads.TryDequeue(out HThread Thread)) - { - UnlockedThreads[Index++] = Thread; - } - - Process.Scheduler.Signal(UnlockedThreads); - } - } - - private void AcquireMutexValue() - { - if (!OwnsMutexValue) - { - while (!Process.Memory.AcquireAddress(MutexAddress)) - { - Thread.Yield(); - } - - OwnsMutexValue = true; - } - } - - private void ReleaseMutexValue() - { - if (OwnsMutexValue) - { - OwnsMutexValue = false; - - Process.Memory.ReleaseAddress(MutexAddress); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Process.cs b/Ryujinx.Core/OsHle/Process.cs index 239b19803..6591ed5aa 100644 --- a/Ryujinx.Core/OsHle/Process.cs +++ b/Ryujinx.Core/OsHle/Process.cs @@ -1,21 +1,25 @@ using ChocolArm64; using ChocolArm64.Events; using ChocolArm64.Memory; +using ChocolArm64.State; using Ryujinx.Core.Loaders; using Ryujinx.Core.Loaders.Executables; using Ryujinx.Core.OsHle.Exceptions; using Ryujinx.Core.OsHle.Handles; -using Ryujinx.Core.OsHle.Svc; +using Ryujinx.Core.OsHle.Kernel; +using Ryujinx.Core.OsHle.Services.Nv; using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Text; namespace Ryujinx.Core.OsHle { class Process : IDisposable { - private const int TlsSize = 0x200; - private const int TotalTlsSlots = 32; + private const int TlsSize = 0x200; + + private const int TotalTlsSlots = (int)MemoryRegions.TlsPagesSize / TlsSize; private const int TickFreq = 19_200_000; @@ -31,21 +35,25 @@ namespace Ryujinx.Core.OsHle public AMemory Memory { get; private set; } - public ServiceMgr Services { get; private set; } - public KProcessScheduler Scheduler { get; private set; } + public KThread ThreadArbiterList { get; set; } + public KProcessHandleTable HandleTable { get; private set; } + public AppletStateMgr AppletState { get; private set; } + private SvcHandler SvcHandler; private ConcurrentDictionary TlsSlots; - private ConcurrentDictionary ThreadsByTpidr; + private ConcurrentDictionary Threads; + + private KThread MainThread; private List Executables; - private HThread MainThread; + private Dictionary SymbolTable; private long ImageBase; @@ -53,24 +61,23 @@ namespace Ryujinx.Core.OsHle private bool Disposed; - public Process(Switch Ns, int ProcessId) + public Process(Switch Ns, KProcessScheduler Scheduler, int ProcessId) { this.Ns = Ns; + this.Scheduler = Scheduler; this.ProcessId = ProcessId; Memory = new AMemory(); - Services = new ServiceMgr(); - HandleTable = new KProcessHandleTable(); - Scheduler = new KProcessScheduler(); + AppletState = new AppletStateMgr(); SvcHandler = new SvcHandler(Ns, this); TlsSlots = new ConcurrentDictionary(); - ThreadsByTpidr = new ConcurrentDictionary(); + Threads = new ConcurrentDictionary(); Executables = new List(); @@ -89,7 +96,7 @@ namespace Ryujinx.Core.OsHle throw new ObjectDisposedException(nameof(Process)); } - Logging.Info($"Image base at 0x{ImageBase:x16}."); + Logging.Info(LogClass.Loader, $"Image base at 0x{ImageBase:x16}."); Executable Executable = new Executable(Program, Memory, ImageBase); @@ -118,21 +125,23 @@ namespace Ryujinx.Core.OsHle return false; } + MakeSymbolTable(); + MapRWMemRegion( MemoryRegions.MainStackAddress, MemoryRegions.MainStackSize, MemoryType.Normal); - + long StackTop = MemoryRegions.MainStackAddress + MemoryRegions.MainStackSize; - int Handle = MakeThread(Executables[0].ImageBase, StackTop, 0, 0, 0); + int Handle = MakeThread(Executables[0].ImageBase, StackTop, 0, 44, 0); if (Handle == -1) { return false; } - MainThread = HandleTable.GetData(Handle); + MainThread = HandleTable.GetData(Handle); if (NeedsHbAbi) { @@ -184,30 +193,32 @@ namespace Ryujinx.Core.OsHle throw new ObjectDisposedException(nameof(Process)); } - AThread Thread = new AThread(GetTranslator(), Memory, EntryPoint); + AThread CpuThread = new AThread(GetTranslator(), Memory, EntryPoint); - HThread ThreadHnd = new HThread(Thread, ProcessorId, Priority); + KThread Thread = new KThread(CpuThread, ProcessorId, Priority); - int Handle = HandleTable.OpenHandle(ThreadHnd); + int Handle = HandleTable.OpenHandle(Thread); - int ThreadId = GetFreeTlsSlot(Thread); + int ThreadId = GetFreeTlsSlot(CpuThread); long Tpidr = MemoryRegions.TlsPagesAddress + ThreadId * TlsSize; - Thread.ThreadState.Break += BreakHandler; - Thread.ThreadState.SvcCall += SvcHandler.SvcCall; - Thread.ThreadState.Undefined += UndefinedHandler; - Thread.ThreadState.ProcessId = ProcessId; - Thread.ThreadState.ThreadId = ThreadId; - Thread.ThreadState.CntfrqEl0 = TickFreq; - Thread.ThreadState.Tpidr = Tpidr; - Thread.ThreadState.X0 = (ulong)ArgsPtr; - Thread.ThreadState.X1 = (ulong)Handle; - Thread.ThreadState.X31 = (ulong)StackTop; + CpuThread.ThreadState.ProcessId = ProcessId; + CpuThread.ThreadState.ThreadId = ThreadId; + CpuThread.ThreadState.CntfrqEl0 = TickFreq; + CpuThread.ThreadState.Tpidr = Tpidr; - Thread.WorkFinished += ThreadFinished; + CpuThread.ThreadState.X0 = (ulong)ArgsPtr; + CpuThread.ThreadState.X1 = (ulong)Handle; + CpuThread.ThreadState.X31 = (ulong)StackTop; - ThreadsByTpidr.TryAdd(Thread.ThreadState.Tpidr, ThreadHnd); + CpuThread.ThreadState.Break += BreakHandler; + CpuThread.ThreadState.SvcCall += SvcHandler.SvcCall; + CpuThread.ThreadState.Undefined += UndefinedHandler; + + CpuThread.WorkFinished += ThreadFinished; + + Threads.TryAdd(CpuThread.ThreadState.Tpidr, Thread); return Handle; } @@ -222,20 +233,23 @@ namespace Ryujinx.Core.OsHle throw new UndefinedInstructionException(e.Position, e.RawOpCode); } + private void MakeSymbolTable() + { + SymbolTable = new Dictionary(); + + foreach (Executable Exe in Executables) + { + foreach (KeyValuePair KV in Exe.SymbolTable) + { + SymbolTable.TryAdd(Exe.ImageBase + KV.Key, KV.Value); + } + } + } + private ATranslator GetTranslator() { if (Translator == null) { - Dictionary SymbolTable = new Dictionary(); - - foreach (Executable Exe in Executables) - { - foreach (KeyValuePair KV in Exe.SymbolTable) - { - SymbolTable.TryAdd(Exe.ImageBase + KV.Key, KV.Value); - } - } - Translator = new ATranslator(SymbolTable); Translator.CpuTrace += CpuTraceHandler; @@ -244,6 +258,16 @@ namespace Ryujinx.Core.OsHle return Translator; } + public void EnableCpuTracing() + { + Translator.EnableCpuTrace = true; + } + + public void DisableCpuTracing() + { + Translator.EnableCpuTrace = false; + } + private void CpuTraceHandler(object sender, ACpuTraceEventArgs e) { string NsoName = string.Empty; @@ -253,22 +277,52 @@ namespace Ryujinx.Core.OsHle if (e.Position >= Executables[Index].ImageBase) { NsoName = $"{(e.Position - Executables[Index].ImageBase):x16}"; - + break; } } - Logging.Trace($"Executing at 0x{e.Position:x16} {e.SubName} {NsoName}"); + Logging.Trace(LogClass.CPU, $"Executing at 0x{e.Position:x16} {e.SubName} {NsoName}"); } - public void EnableCpuTracing() + public void PrintStackTrace(AThreadState ThreadState) { - Translator.EnableCpuTrace = true; + long[] Positions = ThreadState.GetCallStack(); + + StringBuilder Trace = new StringBuilder(); + + Trace.AppendLine("Guest stack trace:"); + + foreach (long Position in Positions) + { + if (!SymbolTable.TryGetValue(Position, out string SubName)) + { + SubName = $"Sub{Position:x16}"; + } + + Trace.AppendLine(" " + SubName + " (" + GetNsoNameAndAddress(Position) + ")"); + } + + Logging.Info(LogClass.CPU, Trace.ToString()); } - public void DisableCpuTracing() + private string GetNsoNameAndAddress(long Position) { - Translator.EnableCpuTrace = false; + string Name = string.Empty; + + for (int Index = Executables.Count - 1; Index >= 0; Index--) + { + if (Position >= Executables[Index].ImageBase) + { + long Offset = Position - Executables[Index].ImageBase; + + Name = $"{Executables[Index].Name}:{Offset:x8}"; + + break; + } + } + + return Name; } private int GetFreeTlsSlot(AThread Thread) @@ -288,9 +342,15 @@ namespace Ryujinx.Core.OsHle { if (sender is AThread Thread) { - Logging.Info($"Thread {Thread.ThreadId} exiting..."); + Logging.Info(LogClass.KernelScheduler, $"Thread {Thread.ThreadId} exiting..."); TlsSlots.TryRemove(GetTlsSlot(Thread.ThreadState.Tpidr), out _); + + KThread KernelThread = GetThread(Thread.ThreadState.Tpidr); + + Scheduler.RemoveThread(KernelThread); + + KernelThread.WaitEvent.Set(); } if (TlsSlots.Count == 0) @@ -300,7 +360,7 @@ namespace Ryujinx.Core.OsHle Dispose(); } - Logging.Info($"No threads running, now exiting Process {ProcessId}..."); + Logging.Info(LogClass.KernelScheduler, $"No threads running, now exiting Process {ProcessId}..."); Ns.Os.ExitProcess(ProcessId); } @@ -311,11 +371,11 @@ namespace Ryujinx.Core.OsHle return (int)((Position - MemoryRegions.TlsPagesAddress) / TlsSize); } - public HThread GetThread(long Tpidr) + public KThread GetThread(long Tpidr) { - if (!ThreadsByTpidr.TryGetValue(Tpidr, out HThread Thread)) + if (!Threads.TryGetValue(Tpidr, out KThread Thread)) { - Logging.Error($"Thread with TPIDR 0x{Tpidr:x16} not found!"); + Logging.Error(LogClass.KernelScheduler, $"Thread with TPIDR 0x{Tpidr:x16} not found!"); } return Thread; @@ -338,20 +398,34 @@ namespace Ryujinx.Core.OsHle { ShouldDispose = true; - Logging.Info($"Process {ProcessId} waiting all threads terminate..."); + Logging.Info(LogClass.KernelScheduler, $"Process {ProcessId} waiting all threads terminate..."); return; } Disposed = true; - - Services.Dispose(); - HandleTable.Dispose(); - Scheduler.Dispose(); + + foreach (object Obj in HandleTable.Clear()) + { + if (Obj is KSession Session) + { + Session.Dispose(); + } + } + + INvDrvServices.Fds.DeleteProcess(this); + + INvDrvServices.NvMaps .DeleteProcess(this); + INvDrvServices.NvMapsById.DeleteProcess(this); + INvDrvServices.NvMapsFb .DeleteProcess(this); + + AppletState.Dispose(); + SvcHandler.Dispose(); + Memory.Dispose(); - Logging.Info($"Process {ProcessId} exiting..."); + Logging.Info(LogClass.KernelScheduler, $"Process {ProcessId} exiting..."); } } } diff --git a/Ryujinx.Core/OsHle/ServiceCtx.cs b/Ryujinx.Core/OsHle/ServiceCtx.cs index 60c378d5a..7716507fe 100644 --- a/Ryujinx.Core/OsHle/ServiceCtx.cs +++ b/Ryujinx.Core/OsHle/ServiceCtx.cs @@ -10,7 +10,7 @@ namespace Ryujinx.Core.OsHle public Switch Ns { get; private set; } public Process Process { get; private set; } public AMemory Memory { get; private set; } - public HSession Session { get; private set; } + public KSession Session { get; private set; } public IpcMessage Request { get; private set; } public IpcMessage Response { get; private set; } public BinaryReader RequestData { get; private set; } @@ -20,7 +20,7 @@ namespace Ryujinx.Core.OsHle Switch Ns, Process Process, AMemory Memory, - HSession Session, + KSession Session, IpcMessage Request, IpcMessage Response, BinaryReader RequestData, diff --git a/Ryujinx.Core/OsHle/ServiceMgr.cs b/Ryujinx.Core/OsHle/ServiceMgr.cs deleted file mode 100644 index f59647afe..000000000 --- a/Ryujinx.Core/OsHle/ServiceMgr.cs +++ /dev/null @@ -1,110 +0,0 @@ -using Ryujinx.Core.OsHle.IpcServices; -using Ryujinx.Core.OsHle.IpcServices.Acc; -using Ryujinx.Core.OsHle.IpcServices.Am; -using Ryujinx.Core.OsHle.IpcServices.Apm; -using Ryujinx.Core.OsHle.IpcServices.Aud; -using Ryujinx.Core.OsHle.IpcServices.Bsd; -using Ryujinx.Core.OsHle.IpcServices.Friend; -using Ryujinx.Core.OsHle.IpcServices.FspSrv; -using Ryujinx.Core.OsHle.IpcServices.Hid; -using Ryujinx.Core.OsHle.IpcServices.Lm; -using Ryujinx.Core.OsHle.IpcServices.Nifm; -using Ryujinx.Core.OsHle.IpcServices.Ns; -using Ryujinx.Core.OsHle.IpcServices.NvServices; -using Ryujinx.Core.OsHle.IpcServices.Pctl; -using Ryujinx.Core.OsHle.IpcServices.Pl; -using Ryujinx.Core.OsHle.IpcServices.Set; -using Ryujinx.Core.OsHle.IpcServices.Sfdnsres; -using Ryujinx.Core.OsHle.IpcServices.Sm; -using Ryujinx.Core.OsHle.IpcServices.Ssl; -using Ryujinx.Core.OsHle.IpcServices.Time; -using Ryujinx.Core.OsHle.IpcServices.Vi; -using System; -using System.Collections.Generic; - -namespace Ryujinx.Core.OsHle -{ - class ServiceMgr : IDisposable - { - private Dictionary Services; - - public ServiceMgr() - { - Services = new Dictionary(); - } - - public IIpcService GetService(string Name) - { - lock (Services) - { - if (!Services.TryGetValue(Name, out IIpcService Service)) - { - switch (Name) - { - case "acc:u0": Service = new ServiceAcc(); break; - case "aoc:u": Service = new ServiceNs(); break; - case "apm": Service = new ServiceApm(); break; - case "apm:p": Service = new ServiceApm(); break; - case "appletOE": Service = new ServiceAppletOE(); break; - case "audout:u": Service = new ServiceAudOut(); break; - case "audren:u": Service = new ServiceAudRen(); break; - case "bsd:s": Service = new ServiceBsd(); break; - case "bsd:u": Service = new ServiceBsd(); break; - case "friend:a": Service = new ServiceFriend(); break; - case "fsp-srv": Service = new ServiceFspSrv(); break; - case "hid": Service = new ServiceHid(); break; - case "lm": Service = new ServiceLm(); break; - case "nifm:u": Service = new ServiceNifm(); break; - case "nvdrv": Service = new ServiceNvDrv(); break; - case "nvdrv:a": Service = new ServiceNvDrv(); break; - case "pctl:a": Service = new ServicePctl(); break; - case "pl:u": Service = new ServicePl(); break; - case "set": Service = new ServiceSet(); break; - case "set:sys": Service = new ServiceSetSys(); break; - case "sfdnsres": Service = new ServiceSfdnsres(); break; - case "sm:": Service = new ServiceSm(); break; - case "ssl": Service = new ServiceSsl(); break; - case "time:s": Service = new ServiceTime(); break; - case "time:u": Service = new ServiceTime(); break; - case "vi:m": Service = new ServiceVi(); break; - case "vi:s": Service = new ServiceVi(); break; - case "vi:u": Service = new ServiceVi(); break; - } - - if (Service == null) - { - throw new NotImplementedException(Name); - } - - Services.Add(Name, Service); - } - - return Service; - } - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool Disposing) - { - if (Disposing) - { - lock (Services) - { - foreach (IIpcService Service in Services.Values) - { - if (Service is IDisposable DisposableSrv) - { - DisposableSrv.Dispose(); - } - } - - Services.Clear(); - } - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Acc/ServiceAcc.cs b/Ryujinx.Core/OsHle/Services/Acc/IAccountServiceForApplication.cs similarity index 62% rename from Ryujinx.Core/OsHle/Services/Acc/ServiceAcc.cs rename to Ryujinx.Core/OsHle/Services/Acc/IAccountServiceForApplication.cs index 8844bb5d1..3ecdf15c5 100644 --- a/Ryujinx.Core/OsHle/Services/Acc/ServiceAcc.cs +++ b/Ryujinx.Core/OsHle/Services/Acc/IAccountServiceForApplication.cs @@ -1,20 +1,19 @@ using Ryujinx.Core.OsHle.Ipc; using System.Collections.Generic; -using static Ryujinx.Core.OsHle.IpcServices.ObjHelper; - -namespace Ryujinx.Core.OsHle.IpcServices.Acc +namespace Ryujinx.Core.OsHle.Services.Acc { - class ServiceAcc : IIpcService + class IAccountServiceForApplication : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; - public ServiceAcc() + public IAccountServiceForApplication() { m_Commands = new Dictionary() { + { 0, GetUserCount }, { 3, ListOpenUsers }, { 5, GetProfile }, { 100, InitializeApplicationInfo }, @@ -22,8 +21,19 @@ namespace Ryujinx.Core.OsHle.IpcServices.Acc }; } + public long GetUserCount(ServiceCtx Context) + { + Context.ResponseData.Write(0); + + Logging.Stub(LogClass.ServiceAcc, "Stubbed"); + + return 0; + } + public long ListOpenUsers(ServiceCtx Context) { + Logging.Stub(LogClass.ServiceAcc, "Stubbed"); + return 0; } @@ -36,6 +46,8 @@ namespace Ryujinx.Core.OsHle.IpcServices.Acc public long InitializeApplicationInfo(ServiceCtx Context) { + Logging.Stub(LogClass.ServiceAcc, "Stubbed"); + return 0; } @@ -46,4 +58,4 @@ namespace Ryujinx.Core.OsHle.IpcServices.Acc return 0; } } -} \ No newline at end of file +} diff --git a/Ryujinx.Core/OsHle/Services/Acc/IManagerForApplication.cs b/Ryujinx.Core/OsHle/Services/Acc/IManagerForApplication.cs index ab491eac7..cc72a64c0 100644 --- a/Ryujinx.Core/OsHle/Services/Acc/IManagerForApplication.cs +++ b/Ryujinx.Core/OsHle/Services/Acc/IManagerForApplication.cs @@ -1,13 +1,13 @@ using Ryujinx.Core.OsHle.Ipc; using System.Collections.Generic; -namespace Ryujinx.Core.OsHle.IpcServices.Acc +namespace Ryujinx.Core.OsHle.Services.Acc { - class IManagerForApplication : IIpcService + class IManagerForApplication : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; public IManagerForApplication() { @@ -19,12 +19,16 @@ namespace Ryujinx.Core.OsHle.IpcServices.Acc } public long CheckAvailability(ServiceCtx Context) - { + { + Logging.Stub(LogClass.ServiceAcc, "Stubbed"); + return 0; } public long GetAccountId(ServiceCtx Context) { + Logging.Stub(LogClass.ServiceAcc, "AccountId = 0xcafeL"); + Context.ResponseData.Write(0xcafeL); return 0; diff --git a/Ryujinx.Core/OsHle/Services/Acc/IProfile.cs b/Ryujinx.Core/OsHle/Services/Acc/IProfile.cs index 77fe2b48c..6f316b1c4 100644 --- a/Ryujinx.Core/OsHle/Services/Acc/IProfile.cs +++ b/Ryujinx.Core/OsHle/Services/Acc/IProfile.cs @@ -1,13 +1,13 @@ using Ryujinx.Core.OsHle.Ipc; using System.Collections.Generic; -namespace Ryujinx.Core.OsHle.IpcServices.Acc +namespace Ryujinx.Core.OsHle.Services.Acc { - class IProfile : IIpcService + class IProfile : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; public IProfile() { @@ -19,6 +19,8 @@ namespace Ryujinx.Core.OsHle.IpcServices.Acc public long GetBase(ServiceCtx Context) { + Logging.Stub(LogClass.ServiceAcc, "Stubbed"); + Context.ResponseData.Write(0L); Context.ResponseData.Write(0L); Context.ResponseData.Write(0L); diff --git a/Ryujinx.Core/OsHle/Services/Am/AmErr.cs b/Ryujinx.Core/OsHle/Services/Am/AmErr.cs new file mode 100644 index 000000000..cb41c2d0f --- /dev/null +++ b/Ryujinx.Core/OsHle/Services/Am/AmErr.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.Core.OsHle.Services.Am +{ + static class AmErr + { + public const int NoMessages = 3; + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Am/FocusState.cs b/Ryujinx.Core/OsHle/Services/Am/FocusState.cs new file mode 100644 index 000000000..2585cf2cc --- /dev/null +++ b/Ryujinx.Core/OsHle/Services/Am/FocusState.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Core.OsHle.Services.Am +{ + enum FocusState + { + InFocus = 1, + OutOfFocus = 2 + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Am/IAllSystemAppletProxiesService.cs b/Ryujinx.Core/OsHle/Services/Am/IAllSystemAppletProxiesService.cs new file mode 100644 index 000000000..0d20f94cb --- /dev/null +++ b/Ryujinx.Core/OsHle/Services/Am/IAllSystemAppletProxiesService.cs @@ -0,0 +1,27 @@ +using Ryujinx.Core.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.Core.OsHle.Services.Am +{ + class IAllSystemAppletProxiesService : IpcService + { + private Dictionary m_Commands; + + public override IReadOnlyDictionary Commands => m_Commands; + + public IAllSystemAppletProxiesService() + { + m_Commands = new Dictionary() + { + { 100, OpenSystemAppletProxy } + }; + } + + public long OpenSystemAppletProxy(ServiceCtx Context) + { + MakeObject(Context, new ISystemAppletProxy()); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Am/IApplicationCreator.cs b/Ryujinx.Core/OsHle/Services/Am/IApplicationCreator.cs new file mode 100644 index 000000000..1114897b3 --- /dev/null +++ b/Ryujinx.Core/OsHle/Services/Am/IApplicationCreator.cs @@ -0,0 +1,20 @@ +using Ryujinx.Core.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.Core.OsHle.Services.Am +{ + class IApplicationCreator : IpcService + { + private Dictionary m_Commands; + + public override IReadOnlyDictionary Commands => m_Commands; + + public IApplicationCreator() + { + m_Commands = new Dictionary() + { + //... + }; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Am/IApplicationFunctions.cs b/Ryujinx.Core/OsHle/Services/Am/IApplicationFunctions.cs index c989cdd44..f1c63fac5 100644 --- a/Ryujinx.Core/OsHle/Services/Am/IApplicationFunctions.cs +++ b/Ryujinx.Core/OsHle/Services/Am/IApplicationFunctions.cs @@ -2,15 +2,13 @@ using Ryujinx.Core.OsHle.Ipc; using System.Collections.Generic; using System.IO; -using static Ryujinx.Core.OsHle.IpcServices.ObjHelper; - -namespace Ryujinx.Core.OsHle.IpcServices.Am +namespace Ryujinx.Core.OsHle.Services.Am { - class IApplicationFunctions : IIpcService + class IApplicationFunctions : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; public IApplicationFunctions() { @@ -20,6 +18,7 @@ namespace Ryujinx.Core.OsHle.IpcServices.Am { 20, EnsureSaveData }, { 21, GetDesiredLanguage }, { 22, SetTerminateResult }, + { 23, GetDisplayVersion }, { 40, NotifyRunning } }; } @@ -39,6 +38,8 @@ namespace Ryujinx.Core.OsHle.IpcServices.Am long UIdLow = Context.RequestData.ReadInt64(); long UIdHigh = Context.RequestData.ReadInt64(); + Logging.Stub(LogClass.ServiceAm, $"UidLow = {UIdLow}, UidHigh = {UIdHigh}"); + Context.ResponseData.Write(0L); return 0; @@ -46,6 +47,8 @@ namespace Ryujinx.Core.OsHle.IpcServices.Am public long GetDesiredLanguage(ServiceCtx Context) { + Logging.Stub(LogClass.ServiceAm, "LanguageId = 1"); + //This is an enumerator where each number is a differnet language. //0 is Japanese and 1 is English, need to figure out the other codes. Context.ResponseData.Write(1L); @@ -60,7 +63,16 @@ namespace Ryujinx.Core.OsHle.IpcServices.Am int Module = ErrorCode & 0xFF; int Description = (ErrorCode >> 9) & 0xFFF; - Logging.Info($"({(ErrorModule)Module}){2000 + Module}-{Description}"); + Logging.Info(LogClass.ServiceAm, $"({(ErrorModule)Module}){2000 + Module}-{Description}"); + + return 0; + } + + public long GetDisplayVersion(ServiceCtx Context) + { + //FIXME: Need to check correct version on a switch. + Context.ResponseData.Write(1L); + Context.ResponseData.Write(0L); return 0; } diff --git a/Ryujinx.Core/OsHle/Services/Am/IApplicationProxy.cs b/Ryujinx.Core/OsHle/Services/Am/IApplicationProxy.cs index 5417d7f04..d9d91600e 100644 --- a/Ryujinx.Core/OsHle/Services/Am/IApplicationProxy.cs +++ b/Ryujinx.Core/OsHle/Services/Am/IApplicationProxy.cs @@ -1,15 +1,13 @@ using Ryujinx.Core.OsHle.Ipc; using System.Collections.Generic; -using static Ryujinx.Core.OsHle.IpcServices.ObjHelper; - -namespace Ryujinx.Core.OsHle.IpcServices.Am +namespace Ryujinx.Core.OsHle.Services.Am { - class IApplicationProxy : IIpcService + class IApplicationProxy : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; public IApplicationProxy() { diff --git a/Ryujinx.Core/OsHle/Services/Am/ServiceAppletOE.cs b/Ryujinx.Core/OsHle/Services/Am/IApplicationProxyService.cs similarity index 64% rename from Ryujinx.Core/OsHle/Services/Am/ServiceAppletOE.cs rename to Ryujinx.Core/OsHle/Services/Am/IApplicationProxyService.cs index b60c93dd5..6e33a1de8 100644 --- a/Ryujinx.Core/OsHle/Services/Am/ServiceAppletOE.cs +++ b/Ryujinx.Core/OsHle/Services/Am/IApplicationProxyService.cs @@ -1,17 +1,15 @@ using Ryujinx.Core.OsHle.Ipc; using System.Collections.Generic; -using static Ryujinx.Core.OsHle.IpcServices.ObjHelper; - -namespace Ryujinx.Core.OsHle.IpcServices.Am +namespace Ryujinx.Core.OsHle.Services.Am { - class ServiceAppletOE : IIpcService + class IApplicationProxyService : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; - public ServiceAppletOE() + public IApplicationProxyService() { m_Commands = new Dictionary() { diff --git a/Ryujinx.Core/OsHle/Services/Am/IAudioController.cs b/Ryujinx.Core/OsHle/Services/Am/IAudioController.cs index 1212f1e24..3b2a69513 100644 --- a/Ryujinx.Core/OsHle/Services/Am/IAudioController.cs +++ b/Ryujinx.Core/OsHle/Services/Am/IAudioController.cs @@ -1,20 +1,71 @@ using Ryujinx.Core.OsHle.Ipc; using System.Collections.Generic; -namespace Ryujinx.Core.OsHle.IpcServices.Am +namespace Ryujinx.Core.OsHle.Services.Am { - class IAudioController : IIpcService + class IAudioController : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; public IAudioController() { m_Commands = new Dictionary() { - //... + { 0, SetExpectedMasterVolume }, + { 1, GetMainAppletExpectedMasterVolume }, + { 2, GetLibraryAppletExpectedMasterVolume }, + { 3, ChangeMainAppletMasterVolume }, + { 4, SetTransparentVolumeRate } }; } + + public long SetExpectedMasterVolume(ServiceCtx Context) + { + float AppletVolume = Context.RequestData.ReadSingle(); + float LibraryAppletVolume = Context.RequestData.ReadSingle(); + + Logging.Stub(LogClass.ServiceAm, "Stubbed"); + + return 0; + } + + public long GetMainAppletExpectedMasterVolume(ServiceCtx Context) + { + Context.ResponseData.Write(1f); + + Logging.Stub(LogClass.ServiceAm, "Stubbed"); + + return 0; + } + + public long GetLibraryAppletExpectedMasterVolume(ServiceCtx Context) + { + Context.ResponseData.Write(1f); + + Logging.Stub(LogClass.ServiceAm, "Stubbed"); + + return 0; + } + + public long ChangeMainAppletMasterVolume(ServiceCtx Context) + { + float Unknown0 = Context.RequestData.ReadSingle(); + long Unknown1 = Context.RequestData.ReadInt64(); + + Logging.Stub(LogClass.ServiceAm, "Stubbed"); + + return 0; + } + + public long SetTransparentVolumeRate(ServiceCtx Context) + { + float Unknown0 = Context.RequestData.ReadSingle(); + + Logging.Stub(LogClass.ServiceAm, "Stubbed"); + + return 0; + } } -} \ No newline at end of file +} diff --git a/Ryujinx.Core/OsHle/Services/Am/ICommonStateGetter.cs b/Ryujinx.Core/OsHle/Services/Am/ICommonStateGetter.cs index 2999bbbae..c1b78e834 100644 --- a/Ryujinx.Core/OsHle/Services/Am/ICommonStateGetter.cs +++ b/Ryujinx.Core/OsHle/Services/Am/ICommonStateGetter.cs @@ -1,13 +1,16 @@ +using Ryujinx.Core.OsHle.Handles; using Ryujinx.Core.OsHle.Ipc; using System.Collections.Generic; -namespace Ryujinx.Core.OsHle.IpcServices.Am +using static Ryujinx.Core.OsHle.ErrorCode; + +namespace Ryujinx.Core.OsHle.Services.Am { - class ICommonStateGetter : IIpcService + class ICommonStateGetter : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; public ICommonStateGetter() { @@ -17,37 +20,32 @@ namespace Ryujinx.Core.OsHle.IpcServices.Am { 1, ReceiveMessage }, { 5, GetOperationMode }, { 6, GetPerformanceMode }, - { 9, GetCurrentFocusState }, + { 8, GetBootMode }, + { 9, GetCurrentFocusState } }; } - private enum FocusState - { - InFocus = 1, - OutOfFocus = 2 - } - - private enum OperationMode - { - Handheld = 0, - Docked = 1 - } - public long GetEventHandle(ServiceCtx Context) { - Context.ResponseData.Write(0L); + KEvent Event = Context.Process.AppletState.MessageEvent; + + int Handle = Context.Process.HandleTable.OpenHandle(Event); + + Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); return 0; } public long ReceiveMessage(ServiceCtx Context) { - //Program expects 0xF at 0x17ae70 on puyo sdk, - //otherwise runs on a infinite loop until it reads said value. - //What it means is still unknown. - Context.ResponseData.Write(0xfL); + if (!Context.Process.AppletState.TryDequeueMessage(out MessageInfo Message)) + { + return MakeError(ErrorModule.Am, AmErr.NoMessages); + } - return 0; //0x680; + Context.ResponseData.Write((int)Message); + + return 0; } public long GetOperationMode(ServiceCtx Context) @@ -59,14 +57,23 @@ namespace Ryujinx.Core.OsHle.IpcServices.Am public long GetPerformanceMode(ServiceCtx Context) { - Context.ResponseData.Write((byte)0); + Context.ResponseData.Write((byte)Apm.PerformanceMode.Handheld); + + return 0; + } + + public long GetBootMode(ServiceCtx Context) + { + Context.ResponseData.Write((byte)0); //Unknown value. + + Logging.Stub(LogClass.ServiceAm, "Stubbed"); return 0; } public long GetCurrentFocusState(ServiceCtx Context) { - Context.ResponseData.Write((byte)FocusState.InFocus); + Context.ResponseData.Write((byte)Context.Process.AppletState.FocusState); return 0; } diff --git a/Ryujinx.Core/OsHle/Services/Am/IDebugFunctions.cs b/Ryujinx.Core/OsHle/Services/Am/IDebugFunctions.cs index 944e58d81..b7c7e9cf6 100644 --- a/Ryujinx.Core/OsHle/Services/Am/IDebugFunctions.cs +++ b/Ryujinx.Core/OsHle/Services/Am/IDebugFunctions.cs @@ -1,13 +1,13 @@ using Ryujinx.Core.OsHle.Ipc; using System.Collections.Generic; -namespace Ryujinx.Core.OsHle.IpcServices.Am +namespace Ryujinx.Core.OsHle.Services.Am { - class IDebugFunctions : IIpcService + class IDebugFunctions : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; public IDebugFunctions() { diff --git a/Ryujinx.Core/OsHle/Services/Am/IDisplayController.cs b/Ryujinx.Core/OsHle/Services/Am/IDisplayController.cs index 979e842a6..a56d17130 100644 --- a/Ryujinx.Core/OsHle/Services/Am/IDisplayController.cs +++ b/Ryujinx.Core/OsHle/Services/Am/IDisplayController.cs @@ -1,13 +1,13 @@ using Ryujinx.Core.OsHle.Ipc; using System.Collections.Generic; -namespace Ryujinx.Core.OsHle.IpcServices.Am +namespace Ryujinx.Core.OsHle.Services.Am { - class IDisplayController : IIpcService + class IDisplayController : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; public IDisplayController() { diff --git a/Ryujinx.Core/OsHle/Services/Am/IGlobalStateController.cs b/Ryujinx.Core/OsHle/Services/Am/IGlobalStateController.cs new file mode 100644 index 000000000..d5810154e --- /dev/null +++ b/Ryujinx.Core/OsHle/Services/Am/IGlobalStateController.cs @@ -0,0 +1,20 @@ +using Ryujinx.Core.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.Core.OsHle.Services.Am +{ + class IGlobalStateController : IpcService + { + private Dictionary m_Commands; + + public override IReadOnlyDictionary Commands => m_Commands; + + public IGlobalStateController() + { + m_Commands = new Dictionary() + { + //... + }; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Am/IHomeMenuFunctions.cs b/Ryujinx.Core/OsHle/Services/Am/IHomeMenuFunctions.cs new file mode 100644 index 000000000..2b81eede5 --- /dev/null +++ b/Ryujinx.Core/OsHle/Services/Am/IHomeMenuFunctions.cs @@ -0,0 +1,45 @@ +using Ryujinx.Core.OsHle.Handles; +using Ryujinx.Core.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.Core.OsHle.Services.Am +{ + class IHomeMenuFunctions : IpcService + { + private Dictionary m_Commands; + + public override IReadOnlyDictionary Commands => m_Commands; + + private KEvent ChannelEvent; + + public IHomeMenuFunctions() + { + m_Commands = new Dictionary() + { + { 10, RequestToGetForeground }, + { 21, GetPopFromGeneralChannelEvent } + }; + + //ToDo: Signal this Event somewhere in future. + ChannelEvent = new KEvent(); + } + + public long RequestToGetForeground(ServiceCtx Context) + { + Logging.Stub(LogClass.ServiceAm, "Stubbed"); + + return 0; + } + + public long GetPopFromGeneralChannelEvent(ServiceCtx Context) + { + int Handle = Context.Process.HandleTable.OpenHandle(ChannelEvent); + + Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); + + Logging.Stub(LogClass.ServiceAm, "Stubbed"); + + return 0; + } + } +} diff --git a/Ryujinx.Core/OsHle/Services/Am/ILibraryAppletCreator.cs b/Ryujinx.Core/OsHle/Services/Am/ILibraryAppletCreator.cs index 9f5b5e69c..7b3e12cc6 100644 --- a/Ryujinx.Core/OsHle/Services/Am/ILibraryAppletCreator.cs +++ b/Ryujinx.Core/OsHle/Services/Am/ILibraryAppletCreator.cs @@ -1,13 +1,13 @@ using Ryujinx.Core.OsHle.Ipc; using System.Collections.Generic; -namespace Ryujinx.Core.OsHle.IpcServices.Am +namespace Ryujinx.Core.OsHle.Services.Am { - class ILibraryAppletCreator : IIpcService + class ILibraryAppletCreator : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; public ILibraryAppletCreator() { diff --git a/Ryujinx.Core/OsHle/Services/Am/ISelfController.cs b/Ryujinx.Core/OsHle/Services/Am/ISelfController.cs index 403e4072d..e508b5f6a 100644 --- a/Ryujinx.Core/OsHle/Services/Am/ISelfController.cs +++ b/Ryujinx.Core/OsHle/Services/Am/ISelfController.cs @@ -1,29 +1,30 @@ using Ryujinx.Core.OsHle.Ipc; using System.Collections.Generic; -namespace Ryujinx.Core.OsHle.IpcServices.Am +namespace Ryujinx.Core.OsHle.Services.Am { - class ISelfController : IIpcService + class ISelfController : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; public ISelfController() { m_Commands = new Dictionary() { - { 1, Exit }, + { 1, LockExit }, { 10, SetScreenShotPermission }, { 11, SetOperationModeChangedNotification }, { 12, SetPerformanceModeChangedNotification }, { 13, SetFocusHandlingMode }, { 14, SetRestartMessageEnabled }, - { 16, SetOutOfFocusSuspendingEnabled } + { 16, SetOutOfFocusSuspendingEnabled }, + { 50, SetHandlesRequestToDisplay } }; } - public long Exit(ServiceCtx Context) + public long LockExit(ServiceCtx Context) { return 0; } @@ -32,6 +33,8 @@ namespace Ryujinx.Core.OsHle.IpcServices.Am { bool Enable = Context.RequestData.ReadByte() != 0 ? true : false; + Logging.Stub(LogClass.ServiceAm, $"ScreenShot Allowed = {Enable}"); + return 0; } @@ -39,6 +42,8 @@ namespace Ryujinx.Core.OsHle.IpcServices.Am { bool Enable = Context.RequestData.ReadByte() != 0 ? true : false; + Logging.Stub(LogClass.ServiceAm, $"OperationMode Changed = {Enable}"); + return 0; } @@ -46,6 +51,8 @@ namespace Ryujinx.Core.OsHle.IpcServices.Am { bool Enable = Context.RequestData.ReadByte() != 0 ? true : false; + Logging.Stub(LogClass.ServiceAm, $"PerformanceMode Changed = {Enable}"); + return 0; } @@ -55,6 +62,8 @@ namespace Ryujinx.Core.OsHle.IpcServices.Am bool Flag2 = Context.RequestData.ReadByte() != 0 ? true : false; bool Flag3 = Context.RequestData.ReadByte() != 0 ? true : false; + Logging.Stub(LogClass.ServiceAm, $"Focus Handling Mode Flags = {{{Flag1}|{Flag2}|{Flag3}}}"); + return 0; } @@ -62,6 +71,8 @@ namespace Ryujinx.Core.OsHle.IpcServices.Am { bool Enable = Context.RequestData.ReadByte() != 0 ? true : false; + Logging.Stub(LogClass.ServiceAm, $"Restart Message Enabled = {Enable}"); + return 0; } @@ -69,6 +80,17 @@ namespace Ryujinx.Core.OsHle.IpcServices.Am { bool Enable = Context.RequestData.ReadByte() != 0 ? true : false; + Logging.Stub(LogClass.ServiceAm, $"Out Of Focus Suspending Enabled = {Enable}"); + + return 0; + } + + public long SetHandlesRequestToDisplay(ServiceCtx Context) + { + bool Enable = Context.RequestData.ReadByte() != 0 ? true : false; + + Logging.Stub(LogClass.ServiceAm, $"HandlesRequestToDisplay Allowed = {Enable}"); + return 0; } } diff --git a/Ryujinx.Core/OsHle/Services/Am/IStorage.cs b/Ryujinx.Core/OsHle/Services/Am/IStorage.cs index 375b960b1..fef4a237e 100644 --- a/Ryujinx.Core/OsHle/Services/Am/IStorage.cs +++ b/Ryujinx.Core/OsHle/Services/Am/IStorage.cs @@ -1,15 +1,13 @@ using Ryujinx.Core.OsHle.Ipc; using System.Collections.Generic; -using static Ryujinx.Core.OsHle.IpcServices.ObjHelper; - -namespace Ryujinx.Core.OsHle.IpcServices.Am +namespace Ryujinx.Core.OsHle.Services.Am { - class IStorage : IIpcService + class IStorage : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; public byte[] Data { get; private set; } diff --git a/Ryujinx.Core/OsHle/Services/Am/IStorageAccessor.cs b/Ryujinx.Core/OsHle/Services/Am/IStorageAccessor.cs index 6d83e6f94..1e17d9ba6 100644 --- a/Ryujinx.Core/OsHle/Services/Am/IStorageAccessor.cs +++ b/Ryujinx.Core/OsHle/Services/Am/IStorageAccessor.cs @@ -3,13 +3,13 @@ using Ryujinx.Core.OsHle.Ipc; using System; using System.Collections.Generic; -namespace Ryujinx.Core.OsHle.IpcServices.Am +namespace Ryujinx.Core.OsHle.Services.Am { - class IStorageAccessor : IIpcService + class IStorageAccessor : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; private IStorage Storage; diff --git a/Ryujinx.Core/OsHle/Services/Am/ISystemAppletProxy.cs b/Ryujinx.Core/OsHle/Services/Am/ISystemAppletProxy.cs new file mode 100644 index 000000000..2d477b34c --- /dev/null +++ b/Ryujinx.Core/OsHle/Services/Am/ISystemAppletProxy.cs @@ -0,0 +1,99 @@ +using Ryujinx.Core.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.Core.OsHle.Services.Am +{ + class ISystemAppletProxy : IpcService + { + private Dictionary m_Commands; + + public override IReadOnlyDictionary Commands => m_Commands; + + public ISystemAppletProxy() + { + m_Commands = new Dictionary() + { + { 0, GetCommonStateGetter }, + { 1, GetSelfController }, + { 2, GetWindowController }, + { 3, GetAudioController }, + { 4, GetDisplayController }, + { 11, GetLibraryAppletCreator }, + { 20, GetHomeMenuFunctions }, + { 21, GetGlobalStateController }, + { 22, GetApplicationCreator }, + { 1000, GetDebugFunctions } + }; + } + + public long GetCommonStateGetter(ServiceCtx Context) + { + MakeObject(Context, new ICommonStateGetter()); + + return 0; + } + + public long GetSelfController(ServiceCtx Context) + { + MakeObject(Context, new ISelfController()); + + return 0; + } + + public long GetWindowController(ServiceCtx Context) + { + MakeObject(Context, new IWindowController()); + + return 0; + } + + public long GetAudioController(ServiceCtx Context) + { + MakeObject(Context, new IAudioController()); + + return 0; + } + + public long GetDisplayController(ServiceCtx Context) + { + MakeObject(Context, new IDisplayController()); + + return 0; + } + + public long GetLibraryAppletCreator(ServiceCtx Context) + { + MakeObject(Context, new ILibraryAppletCreator()); + + return 0; + } + + public long GetHomeMenuFunctions(ServiceCtx Context) + { + MakeObject(Context, new IHomeMenuFunctions()); + + return 0; + } + + public long GetGlobalStateController(ServiceCtx Context) + { + MakeObject(Context, new IGlobalStateController()); + + return 0; + } + + public long GetApplicationCreator(ServiceCtx Context) + { + MakeObject(Context, new IApplicationCreator()); + + return 0; + } + + public long GetDebugFunctions(ServiceCtx Context) + { + MakeObject(Context, new IDebugFunctions()); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Am/IWindowController.cs b/Ryujinx.Core/OsHle/Services/Am/IWindowController.cs index ddc73bced..b494a64bb 100644 --- a/Ryujinx.Core/OsHle/Services/Am/IWindowController.cs +++ b/Ryujinx.Core/OsHle/Services/Am/IWindowController.cs @@ -1,13 +1,13 @@ using Ryujinx.Core.OsHle.Ipc; using System.Collections.Generic; -namespace Ryujinx.Core.OsHle.IpcServices.Am +namespace Ryujinx.Core.OsHle.Services.Am { - class IWindowController : IIpcService + class IWindowController : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; public IWindowController() { @@ -20,6 +20,8 @@ namespace Ryujinx.Core.OsHle.IpcServices.Am public long GetAppletResourceUserId(ServiceCtx Context) { + Logging.Stub(LogClass.ServiceAm, $"Applet Resource Id = 0"); + Context.ResponseData.Write(0L); return 0; @@ -27,6 +29,8 @@ namespace Ryujinx.Core.OsHle.IpcServices.Am public long AcquireForegroundRights(ServiceCtx Context) { + Logging.Stub(LogClass.ServiceAm, "Stubbed"); + return 0; } } diff --git a/Ryujinx.Core/OsHle/Services/Am/MessageInfo.cs b/Ryujinx.Core/OsHle/Services/Am/MessageInfo.cs new file mode 100644 index 000000000..45bf4326f --- /dev/null +++ b/Ryujinx.Core/OsHle/Services/Am/MessageInfo.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Core.OsHle.Services.Am +{ + enum MessageInfo + { + FocusStateChanged = 0xf, + OperationModeChanged = 0x1e, + PerformanceModeChanged = 0x1f + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Am/OperationMode.cs b/Ryujinx.Core/OsHle/Services/Am/OperationMode.cs new file mode 100644 index 000000000..f82a825b4 --- /dev/null +++ b/Ryujinx.Core/OsHle/Services/Am/OperationMode.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Core.OsHle.Services.Am +{ + enum OperationMode + { + Handheld = 0, + Docked = 1 + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Apm/ServiceApm.cs b/Ryujinx.Core/OsHle/Services/Apm/IManager.cs similarity index 64% rename from Ryujinx.Core/OsHle/Services/Apm/ServiceApm.cs rename to Ryujinx.Core/OsHle/Services/Apm/IManager.cs index d6c0400ac..7320328e5 100644 --- a/Ryujinx.Core/OsHle/Services/Apm/ServiceApm.cs +++ b/Ryujinx.Core/OsHle/Services/Apm/IManager.cs @@ -1,17 +1,15 @@ using Ryujinx.Core.OsHle.Ipc; using System.Collections.Generic; -using static Ryujinx.Core.OsHle.IpcServices.ObjHelper; - -namespace Ryujinx.Core.OsHle.IpcServices.Apm +namespace Ryujinx.Core.OsHle.Services.Apm { - class ServiceApm : IIpcService + class IManager : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; - public ServiceApm() + public IManager() { m_Commands = new Dictionary() { diff --git a/Ryujinx.Core/OsHle/Services/Apm/ISession.cs b/Ryujinx.Core/OsHle/Services/Apm/ISession.cs index 500f7596c..bbef100ce 100644 --- a/Ryujinx.Core/OsHle/Services/Apm/ISession.cs +++ b/Ryujinx.Core/OsHle/Services/Apm/ISession.cs @@ -1,26 +1,39 @@ using Ryujinx.Core.OsHle.Ipc; using System.Collections.Generic; -namespace Ryujinx.Core.OsHle.IpcServices.Apm +namespace Ryujinx.Core.OsHle.Services.Apm { - class ISession : IIpcService + class ISession : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; public ISession() { m_Commands = new Dictionary() { - { 0, SetPerformanceConfiguration } + { 0, SetPerformanceConfiguration }, + { 1, GetPerformanceConfiguration } }; } public long SetPerformanceConfiguration(ServiceCtx Context) { - int PerfMode = Context.RequestData.ReadInt32(); - int PerfConfig = Context.RequestData.ReadInt32(); + PerformanceMode PerfMode = (PerformanceMode)Context.RequestData.ReadInt32(); + PerformanceConfiguration PerfConfig = (PerformanceConfiguration)Context.RequestData.ReadInt32(); + + return 0; + } + + public long GetPerformanceConfiguration(ServiceCtx Context) + { + PerformanceMode PerfMode = (PerformanceMode)Context.RequestData.ReadInt32(); + + Context.ResponseData.Write((uint)PerformanceConfiguration.PerformanceConfiguration1); + + Logging.Stub(LogClass.ServiceApm, $"PerformanceMode = {PerfMode}, PerformanceConfiguration =" + + $" {PerformanceConfiguration.PerformanceConfiguration1}"); return 0; } diff --git a/Ryujinx.Core/OsHle/Services/Apm/PerformanceConfiguration.cs b/Ryujinx.Core/OsHle/Services/Apm/PerformanceConfiguration.cs new file mode 100644 index 000000000..5a4d072e2 --- /dev/null +++ b/Ryujinx.Core/OsHle/Services/Apm/PerformanceConfiguration.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Core.OsHle.Services.Apm +{ + enum PerformanceConfiguration : uint + { + PerformanceConfiguration1 = 0x00010000, + PerformanceConfiguration2 = 0x00010001, + PerformanceConfiguration3 = 0x00010002, + PerformanceConfiguration4 = 0x00020000, + PerformanceConfiguration5 = 0x00020001, + PerformanceConfiguration6 = 0x00020002, + PerformanceConfiguration7 = 0x00020003, + PerformanceConfiguration8 = 0x00020004, + PerformanceConfiguration9 = 0x00020005, + PerformanceConfiguration10 = 0x00020006, + PerformanceConfiguration11 = 0x92220007, + PerformanceConfiguration12 = 0x92220008 + } +} diff --git a/Ryujinx.Core/OsHle/Services/Apm/PerformanceMode.cs b/Ryujinx.Core/OsHle/Services/Apm/PerformanceMode.cs new file mode 100644 index 000000000..db6ef4072 --- /dev/null +++ b/Ryujinx.Core/OsHle/Services/Apm/PerformanceMode.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Core.OsHle.Services.Apm +{ + enum PerformanceMode + { + Handheld = 0, + Docked = 1 + } +} diff --git a/Ryujinx.Core/OsHle/Services/Aud/AudioOutData.cs b/Ryujinx.Core/OsHle/Services/Aud/AudioOutData.cs new file mode 100644 index 000000000..9ba935414 --- /dev/null +++ b/Ryujinx.Core/OsHle/Services/Aud/AudioOutData.cs @@ -0,0 +1,14 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Core.OsHle.Services.Aud +{ + [StructLayout(LayoutKind.Sequential)] + struct AudioOutData + { + public long NextBufferPtr; + public long SampleBufferPtr; + public long SampleBufferCapacity; + public long SampleBufferSize; + public long SampleBufferInnerOffset; + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Aud/IAudioDevice.cs b/Ryujinx.Core/OsHle/Services/Aud/IAudioDevice.cs index 9ebf140a2..039a4413f 100644 --- a/Ryujinx.Core/OsHle/Services/Aud/IAudioDevice.cs +++ b/Ryujinx.Core/OsHle/Services/Aud/IAudioDevice.cs @@ -1,23 +1,34 @@ using ChocolArm64.Memory; +using Ryujinx.Core.OsHle.Handles; using Ryujinx.Core.OsHle.Ipc; using System.Collections.Generic; using System.Text; -namespace Ryujinx.Core.OsHle.IpcServices.Aud +namespace Ryujinx.Core.OsHle.Services.Aud { - class IAudioDevice : IIpcService + class IAudioDevice : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; + + private KEvent SystemEvent; public IAudioDevice() { m_Commands = new Dictionary() { - { 0, ListAudioDeviceName }, - { 1, SetAudioDeviceOutputVolume }, + { 0, ListAudioDeviceName }, + { 1, SetAudioDeviceOutputVolume }, + { 3, GetActiveAudioDeviceName }, + { 4, QueryAudioDeviceSystemEvent }, + { 5, GetActiveChannelCount } }; + + SystemEvent = new KEvent(); + + //TODO: We shouldn't be signaling this here. + SystemEvent.WaitEvent.Set(); } public long ListAudioDeviceName(ServiceCtx Context) @@ -55,9 +66,45 @@ namespace Ryujinx.Core.OsHle.IpcServices.Aud long Position = Context.Request.SendBuff[0].Position; long Size = Context.Request.SendBuff[0].Size; - string Name = AMemoryHelper.ReadAsciiString(Context.Memory, Position, (int)Size); + string Name = AMemoryHelper.ReadAsciiString(Context.Memory, Position, Size); + + Logging.Stub(LogClass.ServiceAudio, $"Volume = {Volume}, Position = {Position}, Size = {Size}"); + + return 0; + } + + public long GetActiveAudioDeviceName(ServiceCtx Context) + { + string Name = "FIXME"; + + long Position = Context.Request.ReceiveBuff[0].Position; + long Size = Context.Request.ReceiveBuff[0].Size; + + byte[] Buffer = Encoding.ASCII.GetBytes(Name + '\0'); + + AMemoryHelper.WriteBytes(Context.Memory, Position, Buffer); + + return 0; + } + + public long QueryAudioDeviceSystemEvent(ServiceCtx Context) + { + int Handle = Context.Process.HandleTable.OpenHandle(SystemEvent); + + Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); + + Logging.Stub(LogClass.ServiceAudio, "Stubbed"); + + return 0; + } + + public long GetActiveChannelCount(ServiceCtx Context) + { + Context.ResponseData.Write(2); + + Logging.Stub(LogClass.ServiceAudio, "Stubbed"); return 0; } } -} \ No newline at end of file +} diff --git a/Ryujinx.Core/OsHle/Services/Aud/IAudioOut.cs b/Ryujinx.Core/OsHle/Services/Aud/IAudioOut.cs index 2312920f6..3f7a18c4a 100644 --- a/Ryujinx.Core/OsHle/Services/Aud/IAudioOut.cs +++ b/Ryujinx.Core/OsHle/Services/Aud/IAudioOut.cs @@ -1,145 +1,86 @@ using ChocolArm64.Memory; +using Ryujinx.Audio; using Ryujinx.Core.OsHle.Handles; using Ryujinx.Core.OsHle.Ipc; -using OpenTK.Audio; -using OpenTK.Audio.OpenAL; using System; using System.Collections.Generic; -using System.IO; -namespace Ryujinx.Core.OsHle.IpcServices.Aud +namespace Ryujinx.Core.OsHle.Services.Aud { - class IAudioOut : IIpcService, IDisposable + class IAudioOut : IpcService, IDisposable { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; - public IAudioOut() + private IAalOutput AudioOut; + + private KEvent ReleaseEvent; + + private int Track; + + public IAudioOut(IAalOutput AudioOut, KEvent ReleaseEvent, int Track) { m_Commands = new Dictionary() { - { 0, GetAudioOutState }, - { 1, StartAudioOut }, - { 2, StopAudioOut }, - { 3, AppendAudioOutBuffer }, - { 4, RegisterBufferEvent }, - { 5, GetReleasedAudioOutBuffer }, - { 6, ContainsAudioOutBuffer }, - { 7, AppendAudioOutBuffer_ex }, - { 8, GetReleasedAudioOutBuffer_ex } + { 0, GetAudioOutState }, + { 1, StartAudioOut }, + { 2, StopAudioOut }, + { 3, AppendAudioOutBuffer }, + { 4, RegisterBufferEvent }, + { 5, GetReleasedAudioOutBuffer }, + { 6, ContainsAudioOutBuffer }, + { 7, AppendAudioOutBufferEx }, + { 8, GetReleasedAudioOutBufferEx } }; + + this.AudioOut = AudioOut; + this.ReleaseEvent = ReleaseEvent; + this.Track = Track; } - enum AudioOutState - { - Started, - Stopped - }; - - //IAudioOut - private AudioOutState State = AudioOutState.Stopped; - private Queue BufferIdQueue = new Queue(); - - //OpenAL - private bool OpenALInstalled = true; - private AudioContext AudioCtx; - private int Source; - private int Buffer; - - //Return State of IAudioOut public long GetAudioOutState(ServiceCtx Context) { - Context.ResponseData.Write((int)State); + Context.ResponseData.Write((int)AudioOut.GetState(Track)); return 0; } public long StartAudioOut(ServiceCtx Context) { - if (State == AudioOutState.Stopped) - { - State = AudioOutState.Started; - - try - { - AudioCtx = new AudioContext(); //Create the audio context - } - catch (Exception) - { - Logging.Warn("OpenAL Error! PS: Install OpenAL Core SDK!"); - OpenALInstalled = false; - } - - if (OpenALInstalled) AL.Listener(ALListenerf.Gain, 8.0f); //Add more gain to it - } + AudioOut.Start(Track); return 0; } public long StopAudioOut(ServiceCtx Context) { - if (State == AudioOutState.Started) - { - if (OpenALInstalled) - { - if (AudioCtx == null) //Needed to call the instance of AudioContext() - return 0; - - AL.SourceStop(Source); - AL.DeleteSource(Source); - AL.DeleteBuffers(1, ref Buffer); - } - State = AudioOutState.Stopped; - } + AudioOut.Stop(Track); return 0; } public long AppendAudioOutBuffer(ServiceCtx Context) { - long BufferId = Context.RequestData.ReadInt64(); + long Tag = Context.RequestData.ReadInt64(); - byte[] AudioOutBuffer = AMemoryHelper.ReadBytes(Context.Memory, Context.Request.SendBuff[0].Position, sizeof(long) * 5); + AudioOutData Data = AMemoryHelper.Read( + Context.Memory, + Context.Request.SendBuff[0].Position); + + byte[] Buffer = AMemoryHelper.ReadBytes( + Context.Memory, + Data.SampleBufferPtr, + Data.SampleBufferSize); - using (MemoryStream MS = new MemoryStream(AudioOutBuffer)) - { - BinaryReader Reader = new BinaryReader(MS); - long PointerNextBuffer = Reader.ReadInt64(); - long PointerSampleBuffer = Reader.ReadInt64(); - long CapacitySampleBuffer = Reader.ReadInt64(); - long SizeDataInSampleBuffer = Reader.ReadInt64(); - long OffsetDataInSampleBuffer = Reader.ReadInt64(); - - if (SizeDataInSampleBuffer > 0) - { - BufferIdQueue.Enqueue(BufferId); - - byte[] AudioSampleBuffer = AMemoryHelper.ReadBytes(Context.Memory, PointerSampleBuffer + OffsetDataInSampleBuffer, (int)SizeDataInSampleBuffer); - - if (OpenALInstalled) - { - if (AudioCtx == null) //Needed to call the instance of AudioContext() - return 0; - - EnsureAudioFinalized(); - - Source = AL.GenSource(); - Buffer = AL.GenBuffer(); - - AL.BufferData(Buffer, ALFormat.Stereo16, AudioSampleBuffer, AudioSampleBuffer.Length, 48000); - AL.SourceQueueBuffer(Source, Buffer); - AL.SourcePlay(Source); - } - } - } + AudioOut.AppendBuffer(Track, Tag, Buffer); return 0; } public long RegisterBufferEvent(ServiceCtx Context) { - int Handle = Context.Process.HandleTable.OpenHandle(new HEvent()); + int Handle = Context.Process.HandleTable.OpenHandle(ReleaseEvent); Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); @@ -148,62 +89,65 @@ namespace Ryujinx.Core.OsHle.IpcServices.Aud public long GetReleasedAudioOutBuffer(ServiceCtx Context) { - int ReleasedBuffersCount = 0; + long Position = Context.Request.ReceiveBuff[0].Position; + long Size = Context.Request.ReceiveBuff[0].Size; - for(int i = 0; i < BufferIdQueue.Count; i++) + uint Count = (uint)((ulong)Size >> 3); + + long[] ReleasedBuffers = AudioOut.GetReleasedBuffers(Track, (int)Count); + + for (uint Index = 0; Index < Count; Index++) { - long BufferId = BufferIdQueue.Dequeue(); + long Tag = 0; - AMemoryHelper.WriteBytes(Context.Memory, Context.Request.ReceiveBuff[0].Position + (8 * i), BitConverter.GetBytes(BufferId)); + if (Index < ReleasedBuffers.Length) + { + Tag = ReleasedBuffers[Index]; + } - ReleasedBuffersCount++; + Context.Memory.WriteInt64(Position + Index * 8, Tag); } - Context.ResponseData.Write(ReleasedBuffersCount); + Context.ResponseData.Write(ReleasedBuffers.Length); return 0; } public long ContainsAudioOutBuffer(ServiceCtx Context) { + long Tag = Context.RequestData.ReadInt64(); + + Context.ResponseData.Write(AudioOut.ContainsBuffer(Track, Tag) ? 1 : 0); + return 0; } - public long AppendAudioOutBuffer_ex(ServiceCtx Context) + public long AppendAudioOutBufferEx(ServiceCtx Context) { + Logging.Stub(LogClass.ServiceAudio, "Stubbed"); + return 0; } - public long GetReleasedAudioOutBuffer_ex(ServiceCtx Context) + public long GetReleasedAudioOutBufferEx(ServiceCtx Context) { + Logging.Stub(LogClass.ServiceAudio, "Stubbed"); + return 0; } - private void EnsureAudioFinalized() - { - if (Source != 0 || - Buffer != 0) - { - AL.SourceStop(Source); - AL.SourceUnqueueBuffer(Buffer); - AL.DeleteSource(Source); - AL.DeleteBuffers(1, ref Buffer); - - Source = 0; - Buffer = 0; - } - } - public void Dispose() { Dispose(true); } - protected virtual void Dispose(bool disposing) + protected virtual void Dispose(bool Disposing) { - if (disposing) + if (Disposing) { - EnsureAudioFinalized(); + AudioOut.CloseTrack(Track); + + ReleaseEvent.Dispose(); } } } diff --git a/Ryujinx.Core/OsHle/Services/Aud/IAudioOutManager.cs b/Ryujinx.Core/OsHle/Services/Aud/IAudioOutManager.cs new file mode 100644 index 000000000..b1d20fbe9 --- /dev/null +++ b/Ryujinx.Core/OsHle/Services/Aud/IAudioOutManager.cs @@ -0,0 +1,94 @@ +using ChocolArm64.Memory; +using Ryujinx.Audio; +using Ryujinx.Core.OsHle.Handles; +using Ryujinx.Core.OsHle.Ipc; +using System.Collections.Generic; +using System.Text; + +namespace Ryujinx.Core.OsHle.Services.Aud +{ + class IAudioOutManager : IpcService + { + private Dictionary m_Commands; + + public override IReadOnlyDictionary Commands => m_Commands; + + public IAudioOutManager() + { + m_Commands = new Dictionary() + { + { 0, ListAudioOuts }, + { 1, OpenAudioOut } + }; + } + + public long ListAudioOuts(ServiceCtx Context) + { + long Position = Context.Request.ReceiveBuff[0].Position; + + AMemoryHelper.WriteBytes(Context.Memory, Position, Encoding.ASCII.GetBytes("iface")); + + Context.ResponseData.Write(1); + + return 0; + } + + public long OpenAudioOut(ServiceCtx Context) + { + IAalOutput AudioOut = Context.Ns.AudioOut; + + string DeviceName = AMemoryHelper.ReadAsciiString( + Context.Memory, + Context.Request.SendBuff[0].Position, + Context.Request.SendBuff[0].Size); + + if (DeviceName == string.Empty) + { + DeviceName = "FIXME"; + } + + long DeviceNamePosition = Context.Request.ReceiveBuff[0].Position; + long DeviceNameSize = Context.Request.ReceiveBuff[0].Size; + + byte[] DeviceNameBuffer = Encoding.ASCII.GetBytes(DeviceName); + + if (DeviceName.Length <= DeviceNameSize) + { + AMemoryHelper.WriteBytes(Context.Memory, DeviceNamePosition, DeviceNameBuffer); + } + + int SampleRate = Context.RequestData.ReadInt32(); + int Channels = Context.RequestData.ReadInt32(); + + Channels = (ushort)(Channels >> 16); + + if (SampleRate == 0) + { + SampleRate = 48000; + } + + if (Channels < 1 || Channels > 2) + { + Channels = 2; + } + + KEvent ReleaseEvent = new KEvent(); + + ReleaseCallback Callback = () => + { + ReleaseEvent.WaitEvent.Set(); + }; + + int Track = AudioOut.OpenTrack(SampleRate, Channels, Callback, out AudioFormat Format); + + MakeObject(Context, new IAudioOut(AudioOut, ReleaseEvent, Track)); + + Context.ResponseData.Write(SampleRate); + Context.ResponseData.Write(Channels); + Context.ResponseData.Write((int)Format); + Context.ResponseData.Write((int)PlaybackState.Stopped); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Aud/IAudioRenderer.cs b/Ryujinx.Core/OsHle/Services/Aud/IAudioRenderer.cs index 4d29371fb..d3795b53c 100644 --- a/Ryujinx.Core/OsHle/Services/Aud/IAudioRenderer.cs +++ b/Ryujinx.Core/OsHle/Services/Aud/IAudioRenderer.cs @@ -1,14 +1,17 @@ using Ryujinx.Core.OsHle.Handles; using Ryujinx.Core.OsHle.Ipc; +using System; using System.Collections.Generic; -namespace Ryujinx.Core.OsHle.IpcServices.Aud +namespace Ryujinx.Core.OsHle.Services.Aud { - class IAudioRenderer : IIpcService + class IAudioRenderer : IpcService, IDisposable { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; + + private KEvent UpdateEvent; public IAudioRenderer() { @@ -19,6 +22,8 @@ namespace Ryujinx.Core.OsHle.IpcServices.Aud { 6, StopAudioRenderer }, { 7, QuerySystemEvent } }; + + UpdateEvent = new KEvent(); } public long RequestUpdateAudioRenderer(ServiceCtx Context) @@ -41,26 +46,46 @@ namespace Ryujinx.Core.OsHle.IpcServices.Aud Context.Memory.WriteInt32(Position + Offset, 5); } + //TODO: We shouldn't be signaling this here. + UpdateEvent.WaitEvent.Set(); + return 0; } public long StartAudioRenderer(ServiceCtx Context) { + Logging.Stub(LogClass.ServiceAudio, "Stubbed"); + return 0; } public long StopAudioRenderer(ServiceCtx Context) { + Logging.Stub(LogClass.ServiceAudio, "Stubbed"); + return 0; } public long QuerySystemEvent(ServiceCtx Context) { - int Handle = Context.Process.HandleTable.OpenHandle(new HEvent()); + int Handle = Context.Process.HandleTable.OpenHandle(UpdateEvent); Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); return 0; } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool Disposing) + { + if (Disposing) + { + UpdateEvent.Dispose(); + } + } } } \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Aud/ServiceAudRen.cs b/Ryujinx.Core/OsHle/Services/Aud/IAudioRendererManager.cs similarity index 85% rename from Ryujinx.Core/OsHle/Services/Aud/ServiceAudRen.cs rename to Ryujinx.Core/OsHle/Services/Aud/IAudioRendererManager.cs index c3a0a8b43..dcf3c7b7c 100644 --- a/Ryujinx.Core/OsHle/Services/Aud/ServiceAudRen.cs +++ b/Ryujinx.Core/OsHle/Services/Aud/IAudioRendererManager.cs @@ -1,17 +1,15 @@ using Ryujinx.Core.OsHle.Ipc; using System.Collections.Generic; -using static Ryujinx.Core.OsHle.IpcServices.ObjHelper; - -namespace Ryujinx.Core.OsHle.IpcServices.Aud +namespace Ryujinx.Core.OsHle.Services.Aud { - class ServiceAudRen : IIpcService + class IAudioRendererManager : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; - public ServiceAudRen() + public IAudioRendererManager() { m_Commands = new Dictionary() { @@ -44,6 +42,8 @@ namespace Ryujinx.Core.OsHle.IpcServices.Aud int Unknown2c = Context.RequestData.ReadInt32(); int Rev1Magic = Context.RequestData.ReadInt32(); + Logging.Stub(LogClass.ServiceAudio, "BufferSize = 0x400L"); + Context.ResponseData.Write(0x400L); return 0; diff --git a/Ryujinx.Core/OsHle/Services/Aud/ServiceAudOut.cs b/Ryujinx.Core/OsHle/Services/Aud/ServiceAudOut.cs deleted file mode 100644 index eb923562b..000000000 --- a/Ryujinx.Core/OsHle/Services/Aud/ServiceAudOut.cs +++ /dev/null @@ -1,57 +0,0 @@ -using ChocolArm64.Memory; -using Ryujinx.Core.OsHle.Ipc; -using System.Collections.Generic; -using System.Text; - -using static Ryujinx.Core.OsHle.IpcServices.ObjHelper; - -namespace Ryujinx.Core.OsHle.IpcServices.Aud -{ - class ServiceAudOut : IIpcService - { - private Dictionary m_Commands; - - public IReadOnlyDictionary Commands => m_Commands; - - public ServiceAudOut() - { - m_Commands = new Dictionary() - { - { 0, ListAudioOuts }, - { 1, OpenAudioOut }, - }; - } - - public long ListAudioOuts(ServiceCtx Context) - { - long Position = Context.Request.ReceiveBuff[0].Position; - - AMemoryHelper.WriteBytes(Context.Memory, Position, Encoding.ASCII.GetBytes("iface")); - - Context.ResponseData.Write(1); - - return 0; - } - - public long OpenAudioOut(ServiceCtx Context) - { - MakeObject(Context, new IAudioOut()); - - Context.ResponseData.Write(48000); //Sample Rate - Context.ResponseData.Write(2); //Channel Count - Context.ResponseData.Write(2); //PCM Format - /* - 0 - Invalid - 1 - INT8 - 2 - INT16 - 3 - INT24 - 4 - INT32 - 5 - PCM Float - 6 - ADPCM - */ - Context.ResponseData.Write(0); //Unknown - - return 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Bsd/BsdError.cs b/Ryujinx.Core/OsHle/Services/Bsd/BsdError.cs new file mode 100644 index 000000000..a1ac0433b --- /dev/null +++ b/Ryujinx.Core/OsHle/Services/Bsd/BsdError.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Core.OsHle.Services.Bsd +{ + //bsd_errno == (SocketException.ErrorCode - 10000) + public enum BsdError + { + Timeout = 60 + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Bsd/BsdSocket.cs b/Ryujinx.Core/OsHle/Services/Bsd/BsdSocket.cs new file mode 100644 index 000000000..592b07d95 --- /dev/null +++ b/Ryujinx.Core/OsHle/Services/Bsd/BsdSocket.cs @@ -0,0 +1,18 @@ +using System.Net; +using System.Net.Sockets; + +namespace Ryujinx.Core.OsHle.Services.Bsd +{ + class BsdSocket + { + public int Family; + public int Type; + public int Protocol; + + public IPAddress IpAddress; + + public IPEndPoint RemoteEP; + + public Socket Handle; + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Bsd/ServiceBsd.cs b/Ryujinx.Core/OsHle/Services/Bsd/IClient.cs similarity index 66% rename from Ryujinx.Core/OsHle/Services/Bsd/ServiceBsd.cs rename to Ryujinx.Core/OsHle/Services/Bsd/IClient.cs index da1e51e17..ccfb9147c 100644 --- a/Ryujinx.Core/OsHle/Services/Bsd/ServiceBsd.cs +++ b/Ryujinx.Core/OsHle/Services/Bsd/IClient.cs @@ -1,65 +1,23 @@ using ChocolArm64.Memory; using Ryujinx.Core.OsHle.Ipc; using Ryujinx.Core.OsHle.Utilities; -using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Sockets; using System.Threading.Tasks; -namespace Ryujinx.Core.OsHle.IpcServices.Bsd +namespace Ryujinx.Core.OsHle.Services.Bsd { - - //bsd_errno == (SocketException.ErrorCode - 10000) - //https://github.com/freebsd/freebsd/blob/master/sys/sys/errno.h - public enum BsdError - { - ENOTSOCK = 38, /* Socket operation on non-socket */ - EDESTADDRREQ = 39, /* Destination address required */ - EMSGSIZE = 40, /* Message too long */ - EPROTOTYPE = 41, /* Protocol wrong type for socket */ - ENOPROTOOPT = 42, /* Protocol not available */ - EPROTONOSUPPORT = 43, /* Protocol not supported */ - ESOCKTNOSUPPORT = 44, /* Socket type not supported */ - EOPNOTSUPP = 45, /* Operation not supported */ - EPFNOSUPPORT = 46, /* Protocol family not supported */ - EAFNOSUPPORT = 47, /* Address family not supported by protocol family */ - EADDRINUSE = 48, /* Address already in use */ - EADDRNOTAVAIL = 49, /* Can't assign requested address */ - ENETDOWN = 50, /* Network is down */ - ENETUNREACH = 51, /* Network is unreachable */ - ENETRESET = 52, /* Network dropped connection on reset */ - ECONNABORTED = 53, /* Software caused connection abort */ - ECONNRESET = 54, /* Connection reset by peer */ - ENOBUFS = 55, /* No buffer space available */ - EISCONN = 56, /* Socket is already connected */ - ENOTCONN = 57, /* Socket is not connected */ - ESHUTDOWN = 58, /* Can't send after socket shutdown */ - ETOOMANYREFS = 59, /* Too many references: can't splice */ - ETIMEDOUT = 60, /* Operation timed out */ - ECONNREFUSED = 61 /* Connection refused */ - } - - class SocketBsd - { - public int Family; - public int Type; - public int Protocol; - public IPAddress IpAddress; - public IPEndPoint RemoteEP; - public Socket Handle; - } - - class ServiceBsd : IIpcService + class IClient : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; - private List Sockets = new List(); + private List Sockets = new List(); - public ServiceBsd() + public IClient() { m_Commands = new Dictionary() { @@ -95,11 +53,6 @@ namespace Ryujinx.Core.OsHle.IpcServices.Bsd } BsdBufferConfig; */ - long Pid = Context.RequestData.ReadInt64(); - long TransferMemorySize = Context.RequestData.ReadInt64(); - - // Two other args are unknown! - Context.ResponseData.Write(0); //Todo: Stub @@ -118,18 +71,18 @@ namespace Ryujinx.Core.OsHle.IpcServices.Bsd //(u32 domain, u32 type, u32 protocol) -> (i32 ret, u32 bsd_errno) public long Socket(ServiceCtx Context) { - SocketBsd NewBSDSocket = new SocketBsd + BsdSocket NewBsdSocket = new BsdSocket { Family = Context.RequestData.ReadInt32(), Type = Context.RequestData.ReadInt32(), Protocol = Context.RequestData.ReadInt32() }; - Sockets.Add(NewBSDSocket); + Sockets.Add(NewBsdSocket); - Sockets[Sockets.Count - 1].Handle = new Socket((AddressFamily)Sockets[Sockets.Count - 1].Family, - (SocketType)Sockets[Sockets.Count - 1].Type, - (ProtocolType)Sockets[Sockets.Count - 1].Protocol); + NewBsdSocket.Handle = new Socket((AddressFamily)NewBsdSocket.Family, + (SocketType)NewBsdSocket.Type, + (ProtocolType)NewBsdSocket.Protocol); Context.ResponseData.Write(Sockets.Count - 1); Context.ResponseData.Write(0); @@ -149,12 +102,13 @@ namespace Ryujinx.Core.OsHle.IpcServices.Bsd //https://github.com/TuxSH/ftpd/blob/switch_pr/source/ftp.c#L1634 //https://linux.die.net/man/2/poll - byte[] SentBuffer = AMemoryHelper.ReadBytes(Context.Memory, - Context.Request.SendBuff[0].Position, - (int)Context.Request.SendBuff[0].Size); - int SocketId = Get32(SentBuffer, 0); - short RequestedEvents = (short)Get16(SentBuffer, 4); - short ReturnedEvents = (short)Get16(SentBuffer, 6); + byte[] SentBuffer = AMemoryHelper.ReadBytes(Context.Memory, + Context.Request.SendBuff[0].Position, + Context.Request.SendBuff[0].Size); + + int SocketId = Get32(SentBuffer, 0); + int RequestedEvents = Get16(SentBuffer, 4); + int ReturnedEvents = Get16(SentBuffer, 6); //Todo: Stub - Need to implemented the Type-22 buffer. @@ -167,18 +121,20 @@ namespace Ryujinx.Core.OsHle.IpcServices.Bsd //(u32 socket, u32 flags) -> (i32 ret, u32 bsd_errno, buffer message) public long Recv(ServiceCtx Context) { + int SocketId = Context.RequestData.ReadInt32(); + int SocketFlags = Context.RequestData.ReadInt32(); + + byte[] ReceivedBuffer = new byte[Context.Request.ReceiveBuff[0].Size]; + try { - int SocketId = Context.RequestData.ReadInt32(); - int SocketFlags = Context.RequestData.ReadInt32(); - byte[] ReceivedBuffer = new byte[Context.Request.ReceiveBuff[0].Size]; - int ReadedBytes = Sockets[SocketId].Handle.Receive(ReceivedBuffer); + int BytesRead = Sockets[SocketId].Handle.Receive(ReceivedBuffer); //Logging.Debug("Received Buffer:" + Environment.NewLine + Logging.HexDump(ReceivedBuffer)); AMemoryHelper.WriteBytes(Context.Memory, Context.Request.ReceiveBuff[0].Position, ReceivedBuffer); - Context.ResponseData.Write(ReadedBytes); + Context.ResponseData.Write(BytesRead); Context.ResponseData.Write(0); } catch (SocketException Ex) @@ -193,15 +149,16 @@ namespace Ryujinx.Core.OsHle.IpcServices.Bsd //(u32 socket, u32 flags, buffer) -> (i32 ret, u32 bsd_errno) public long Send(ServiceCtx Context) { - int SocketId = Context.RequestData.ReadInt32(); - int SocketFlags = Context.RequestData.ReadInt32(); - byte[] SentBuffer = AMemoryHelper.ReadBytes(Context.Memory, - Context.Request.SendBuff[0].Position, - (int)Context.Request.SendBuff[0].Size); + int SocketId = Context.RequestData.ReadInt32(); + int SocketFlags = Context.RequestData.ReadInt32(); + + byte[] SentBuffer = AMemoryHelper.ReadBytes(Context.Memory, + Context.Request.SendBuff[0].Position, + Context.Request.SendBuff[0].Size); try { - //Logging.Debug("Sended Buffer:" + Environment.NewLine + Logging.HexDump(SendedBuffer)); + //Logging.Debug("Sent Buffer:" + Environment.NewLine + Logging.HexDump(SentBuffer)); int BytesSent = Sockets[SocketId].Handle.Send(SentBuffer); @@ -220,14 +177,16 @@ namespace Ryujinx.Core.OsHle.IpcServices.Bsd //(u32 socket, u32 flags, buffer, buffer) -> (i32 ret, u32 bsd_errno) public long SendTo(ServiceCtx Context) { - int SocketId = Context.RequestData.ReadInt32(); - int SocketFlags = Context.RequestData.ReadInt32(); - byte[] SentBuffer = AMemoryHelper.ReadBytes(Context.Memory, - Context.Request.SendBuff[0].Position, - (int)Context.Request.SendBuff[0].Size); - byte[] AddressBuffer = AMemoryHelper.ReadBytes(Context.Memory, - Context.Request.SendBuff[1].Position, - (int)Context.Request.SendBuff[1].Size); + int SocketId = Context.RequestData.ReadInt32(); + int SocketFlags = Context.RequestData.ReadInt32(); + + byte[] SentBuffer = AMemoryHelper.ReadBytes(Context.Memory, + Context.Request.SendBuff[0].Position, + Context.Request.SendBuff[0].Size); + + byte[] AddressBuffer = AMemoryHelper.ReadBytes(Context.Memory, + Context.Request.SendBuff[1].Position, + Context.Request.SendBuff[1].Size); if (!Sockets[SocketId].Handle.Connected) { @@ -246,7 +205,7 @@ namespace Ryujinx.Core.OsHle.IpcServices.Bsd try { - //Logging.Debug("Sended Buffer:" + Environment.NewLine + Logging.HexDump(SendedBuffer)); + //Logging.Debug("Sent Buffer:" + Environment.NewLine + Logging.HexDump(SentBuffer)); int BytesSent = Sockets[SocketId].Handle.Send(SentBuffer); @@ -265,12 +224,13 @@ namespace Ryujinx.Core.OsHle.IpcServices.Bsd //(u32 socket) -> (i32 ret, u32 bsd_errno, u32 addrlen, buffer addr) public long Accept(ServiceCtx Context) { - int SocketId = Context.RequestData.ReadInt32(); + int SocketId = Context.RequestData.ReadInt32(); + long AddrBufferPtr = Context.Request.ReceiveBuff[0].Position; Socket HandleAccept = null; - var TimeOut = Task.Factory.StartNew(() => + Task TimeOut = Task.Factory.StartNew(() => { try { @@ -287,28 +247,28 @@ namespace Ryujinx.Core.OsHle.IpcServices.Bsd if (HandleAccept != null) { - SocketBsd NewBSDSocket = new SocketBsd + BsdSocket NewBsdSocket = new BsdSocket { IpAddress = ((IPEndPoint)Sockets[SocketId].Handle.LocalEndPoint).Address, RemoteEP = ((IPEndPoint)Sockets[SocketId].Handle.LocalEndPoint), Handle = HandleAccept }; - Sockets.Add(NewBSDSocket); + Sockets.Add(NewBsdSocket); using (MemoryStream MS = new MemoryStream()) { BinaryWriter Writer = new BinaryWriter(MS); Writer.Write((byte)0); - Writer.Write((byte)Sockets[Sockets.Count - 1].Handle.AddressFamily); - Writer.Write((Int16)((IPEndPoint)Sockets[Sockets.Count - 1].Handle.LocalEndPoint).Port); - string[] IpAdress = Sockets[Sockets.Count - 1].IpAddress.ToString().Split('.'); - Writer.Write(byte.Parse(IpAdress[0])); - Writer.Write(byte.Parse(IpAdress[1])); - Writer.Write(byte.Parse(IpAdress[2])); - Writer.Write(byte.Parse(IpAdress[3])); + Writer.Write((byte)NewBsdSocket.Handle.AddressFamily); + + Writer.Write((short)((IPEndPoint)NewBsdSocket.Handle.LocalEndPoint).Port); + + byte[] IpAddress = NewBsdSocket.IpAddress.GetAddressBytes(); + + Writer.Write(IpAddress); AMemoryHelper.WriteBytes(Context.Memory, AddrBufferPtr, MS.ToArray()); @@ -320,7 +280,7 @@ namespace Ryujinx.Core.OsHle.IpcServices.Bsd else { Context.ResponseData.Write(-1); - Context.ResponseData.Write((int)BsdError.ETIMEDOUT); + Context.ResponseData.Write((int)BsdError.Timeout); } return 0; @@ -331,9 +291,9 @@ namespace Ryujinx.Core.OsHle.IpcServices.Bsd { int SocketId = Context.RequestData.ReadInt32(); - byte[] AddressBuffer = AMemoryHelper.ReadBytes(Context.Memory, - Context.Request.SendBuff[0].Position, - (int)Context.Request.SendBuff[0].Size); + byte[] AddressBuffer = AMemoryHelper.ReadBytes(Context.Memory, + Context.Request.SendBuff[0].Position, + Context.Request.SendBuff[0].Size); try { @@ -356,9 +316,9 @@ namespace Ryujinx.Core.OsHle.IpcServices.Bsd { int SocketId = Context.RequestData.ReadInt32(); - byte[] AddressBuffer = AMemoryHelper.ReadBytes(Context.Memory, - Context.Request.SendBuff[0].Position, - (int)Context.Request.SendBuff[0].Size); + byte[] AddressBuffer = AMemoryHelper.ReadBytes(Context.Memory, + Context.Request.SendBuff[0].Position, + Context.Request.SendBuff[0].Size); try { @@ -404,19 +364,20 @@ namespace Ryujinx.Core.OsHle.IpcServices.Bsd //(u32 socket, u32 level, u32 option_name, buffer) -> (i32 ret, u32 bsd_errno) public long SetSockOpt(ServiceCtx Context) { - int SocketId = Context.RequestData.ReadInt32(); - int SocketLevel = Context.RequestData.ReadInt32(); - int SocketOptionName = Context.RequestData.ReadInt32(); + int SocketId = Context.RequestData.ReadInt32(); - byte[] SocketOptionValue = AMemoryHelper.ReadBytes(Context.Memory, - Context.Request.PtrBuff[0].Position, + SocketOptionLevel SocketLevel = (SocketOptionLevel)Context.RequestData.ReadInt32(); + SocketOptionName SocketOptionName = (SocketOptionName)Context.RequestData.ReadInt32(); + + byte[] SocketOptionValue = AMemoryHelper.ReadBytes(Context.Memory, + Context.Request.PtrBuff[0].Position, Context.Request.PtrBuff[0].Size); + int OptionValue = Get32(SocketOptionValue, 0); + try { - Sockets[SocketId].Handle.SetSocketOption((SocketOptionLevel)SocketLevel, - (SocketOptionName)SocketOptionName, - Get32(SocketOptionValue, 0)); + Sockets[SocketId].Handle.SetSocketOption(SocketLevel, SocketOptionName, OptionValue); Context.ResponseData.Write(0); Context.ResponseData.Write(0); @@ -461,12 +422,13 @@ namespace Ryujinx.Core.OsHle.IpcServices.Bsd int Size = Reader.ReadByte(); int Family = Reader.ReadByte(); int Port = EndianSwap.Swap16(Reader.ReadInt16()); - string IpAddress = Reader.ReadByte().ToString() + - "." + Reader.ReadByte().ToString() + - "." + Reader.ReadByte().ToString() + - "." + Reader.ReadByte().ToString(); - Logging.Debug($"Try to connect to {IpAddress}:{Port}"); + string IpAddress = Reader.ReadByte().ToString() + "." + + Reader.ReadByte().ToString() + "." + + Reader.ReadByte().ToString() + "." + + Reader.ReadByte().ToString(); + + Logging.Debug(LogClass.ServiceBsd, $"Try to connect to {IpAddress}:{Port}"); Sockets[SocketId].IpAddress = IPAddress.Parse(IpAddress); Sockets[SocketId].RemoteEP = new IPEndPoint(Sockets[SocketId].IpAddress, Port); diff --git a/Ryujinx.Core/OsHle/Services/Caps/IAlbumAccessorService.cs b/Ryujinx.Core/OsHle/Services/Caps/IAlbumAccessorService.cs new file mode 100644 index 000000000..d92f3e53c --- /dev/null +++ b/Ryujinx.Core/OsHle/Services/Caps/IAlbumAccessorService.cs @@ -0,0 +1,20 @@ +using Ryujinx.Core.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.Core.OsHle.Services.Caps +{ + class IAlbumAccessorService : IpcService + { + private Dictionary m_Commands; + + public override IReadOnlyDictionary Commands => m_Commands; + + public IAlbumAccessorService() + { + m_Commands = new Dictionary() + { + //... + }; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Caps/IScreenshotService.cs b/Ryujinx.Core/OsHle/Services/Caps/IScreenshotService.cs new file mode 100644 index 000000000..af9b53a8b --- /dev/null +++ b/Ryujinx.Core/OsHle/Services/Caps/IScreenshotService.cs @@ -0,0 +1,20 @@ +using Ryujinx.Core.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.Core.OsHle.Services.Caps +{ + class IScreenshotService : IpcService + { + private Dictionary m_Commands; + + public override IReadOnlyDictionary Commands => m_Commands; + + public IScreenshotService() + { + m_Commands = new Dictionary() + { + //... + }; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Friend/IFriendService.cs b/Ryujinx.Core/OsHle/Services/Friend/IFriendService.cs index e3e03da85..c6e29f860 100644 --- a/Ryujinx.Core/OsHle/Services/Friend/IFriendService.cs +++ b/Ryujinx.Core/OsHle/Services/Friend/IFriendService.cs @@ -1,13 +1,13 @@ using Ryujinx.Core.OsHle.Ipc; using System.Collections.Generic; -namespace Ryujinx.Core.OsHle.IpcServices.Friend +namespace Ryujinx.Core.OsHle.Services.Friend { - class IFriendService : IIpcService + class IFriendService : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; public IFriendService() { diff --git a/Ryujinx.Core/OsHle/Services/Friend/ServiceFriend.cs b/Ryujinx.Core/OsHle/Services/Friend/IServiceCreator.cs similarity index 64% rename from Ryujinx.Core/OsHle/Services/Friend/ServiceFriend.cs rename to Ryujinx.Core/OsHle/Services/Friend/IServiceCreator.cs index 674877f67..2c66d9658 100644 --- a/Ryujinx.Core/OsHle/Services/Friend/ServiceFriend.cs +++ b/Ryujinx.Core/OsHle/Services/Friend/IServiceCreator.cs @@ -1,17 +1,15 @@ using Ryujinx.Core.OsHle.Ipc; using System.Collections.Generic; -using static Ryujinx.Core.OsHle.IpcServices.ObjHelper; - -namespace Ryujinx.Core.OsHle.IpcServices.Friend +namespace Ryujinx.Core.OsHle.Services.Friend { - class ServiceFriend : IIpcService + class IServiceCreator : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; - public ServiceFriend() + public IServiceCreator() { m_Commands = new Dictionary() { diff --git a/Ryujinx.Core/OsHle/Services/FspSrv/FsErr.cs b/Ryujinx.Core/OsHle/Services/FspSrv/FsErr.cs index 656d529f2..762f65511 100644 --- a/Ryujinx.Core/OsHle/Services/FspSrv/FsErr.cs +++ b/Ryujinx.Core/OsHle/Services/FspSrv/FsErr.cs @@ -1,4 +1,4 @@ -namespace Ryujinx.Core.OsHle.IpcServices.FspSrv +namespace Ryujinx.Core.OsHle.Services.FspSrv { static class FsErr { diff --git a/Ryujinx.Core/OsHle/Services/FspSrv/IDirectory.cs b/Ryujinx.Core/OsHle/Services/FspSrv/IDirectory.cs index 54dbec746..fab8c676d 100644 --- a/Ryujinx.Core/OsHle/Services/FspSrv/IDirectory.cs +++ b/Ryujinx.Core/OsHle/Services/FspSrv/IDirectory.cs @@ -5,15 +5,15 @@ using System.Collections.Generic; using System.IO; using System.Text; -namespace Ryujinx.Core.OsHle.IpcServices.FspSrv +namespace Ryujinx.Core.OsHle.Services.FspSrv { - class IDirectory : IIpcService, IDisposable + class IDirectory : IpcService, IDisposable { private const int DirectoryEntrySize = 0x310; private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; private List DirectoryEntries; diff --git a/Ryujinx.Core/OsHle/Services/FspSrv/IFile.cs b/Ryujinx.Core/OsHle/Services/FspSrv/IFile.cs index ac2100f29..bd7d138fd 100644 --- a/Ryujinx.Core/OsHle/Services/FspSrv/IFile.cs +++ b/Ryujinx.Core/OsHle/Services/FspSrv/IFile.cs @@ -4,13 +4,13 @@ using System; using System.Collections.Generic; using System.IO; -namespace Ryujinx.Core.OsHle.IpcServices.FspSrv +namespace Ryujinx.Core.OsHle.Services.FspSrv { - class IFile : IIpcService, IDisposable + class IFile : IpcService, IDisposable { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; private Stream BaseStream; @@ -62,7 +62,7 @@ namespace Ryujinx.Core.OsHle.IpcServices.FspSrv long Offset = Context.RequestData.ReadInt64(); long Size = Context.RequestData.ReadInt64(); - byte[] Data = AMemoryHelper.ReadBytes(Context.Memory, Position, (int)Size); + byte[] Data = AMemoryHelper.ReadBytes(Context.Memory, Position, Size); BaseStream.Seek(Offset, SeekOrigin.Begin); BaseStream.Write(Data, 0, (int)Size); diff --git a/Ryujinx.Core/OsHle/Services/FspSrv/IFileSystem.cs b/Ryujinx.Core/OsHle/Services/FspSrv/IFileSystem.cs index 62bcb8e8b..f65b89ef6 100644 --- a/Ryujinx.Core/OsHle/Services/FspSrv/IFileSystem.cs +++ b/Ryujinx.Core/OsHle/Services/FspSrv/IFileSystem.cs @@ -5,15 +5,14 @@ using System.IO; using System.Text; using static Ryujinx.Core.OsHle.ErrorCode; -using static Ryujinx.Core.OsHle.IpcServices.ObjHelper; -namespace Ryujinx.Core.OsHle.IpcServices.FspSrv +namespace Ryujinx.Core.OsHle.Services.FspSrv { - class IFileSystem : IIpcService + class IFileSystem : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; private HashSet OpenPaths; diff --git a/Ryujinx.Core/OsHle/Services/FspSrv/ServiceFspSrv.cs b/Ryujinx.Core/OsHle/Services/FspSrv/IFileSystemProxy.cs similarity index 53% rename from Ryujinx.Core/OsHle/Services/FspSrv/ServiceFspSrv.cs rename to Ryujinx.Core/OsHle/Services/FspSrv/IFileSystemProxy.cs index 991f40272..3afee1a16 100644 --- a/Ryujinx.Core/OsHle/Services/FspSrv/ServiceFspSrv.cs +++ b/Ryujinx.Core/OsHle/Services/FspSrv/IFileSystemProxy.cs @@ -1,42 +1,40 @@ using Ryujinx.Core.OsHle.Ipc; using System.Collections.Generic; -using static Ryujinx.Core.OsHle.IpcServices.ObjHelper; - -namespace Ryujinx.Core.OsHle.IpcServices.FspSrv +namespace Ryujinx.Core.OsHle.Services.FspSrv { - class ServiceFspSrv : IIpcService + class IFileSystemProxy : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; - public ServiceFspSrv() + public IFileSystemProxy() { m_Commands = new Dictionary() { - { 1, Initialize }, - { 18, MountSdCard }, - { 51, MountSaveData }, - { 200, OpenDataStorageByCurrentProcess }, - { 203, OpenRomStorage }, - { 1005, GetGlobalAccessLogMode } + { 1, SetCurrentProcess }, + { 18, OpenSdCardFileSystem }, + { 51, OpenSaveDataFileSystem }, + { 200, OpenDataStorageByCurrentProcess }, + { 203, OpenPatchDataStorageByCurrentProcess }, + { 1005, GetGlobalAccessLogMode } }; } - public long Initialize(ServiceCtx Context) + public long SetCurrentProcess(ServiceCtx Context) { return 0; } - public long MountSdCard(ServiceCtx Context) + public long OpenSdCardFileSystem(ServiceCtx Context) { MakeObject(Context, new IFileSystem(Context.Ns.VFs.GetSdCardPath())); return 0; } - public long MountSaveData(ServiceCtx Context) + public long OpenSaveDataFileSystem(ServiceCtx Context) { MakeObject(Context, new IFileSystem(Context.Ns.VFs.GetGameSavesPath())); @@ -50,7 +48,7 @@ namespace Ryujinx.Core.OsHle.IpcServices.FspSrv return 0; } - public long OpenRomStorage(ServiceCtx Context) + public long OpenPatchDataStorageByCurrentProcess(ServiceCtx Context) { MakeObject(Context, new IStorage(Context.Ns.VFs.RomFs)); @@ -62,6 +60,6 @@ namespace Ryujinx.Core.OsHle.IpcServices.FspSrv Context.ResponseData.Write(0); return 0; - } + } } } \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/FspSrv/IStorage.cs b/Ryujinx.Core/OsHle/Services/FspSrv/IStorage.cs index 297461a04..05181c649 100644 --- a/Ryujinx.Core/OsHle/Services/FspSrv/IStorage.cs +++ b/Ryujinx.Core/OsHle/Services/FspSrv/IStorage.cs @@ -3,13 +3,13 @@ using Ryujinx.Core.OsHle.Ipc; using System.Collections.Generic; using System.IO; -namespace Ryujinx.Core.OsHle.IpcServices.FspSrv +namespace Ryujinx.Core.OsHle.Services.FspSrv { - class IStorage : IIpcService + class IStorage : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; private Stream BaseStream; diff --git a/Ryujinx.Core/OsHle/Services/Hid/IActiveVibrationDeviceList.cs b/Ryujinx.Core/OsHle/Services/Hid/IActiveVibrationDeviceList.cs index f6596f429..d02fa17e7 100644 --- a/Ryujinx.Core/OsHle/Services/Hid/IActiveVibrationDeviceList.cs +++ b/Ryujinx.Core/OsHle/Services/Hid/IActiveVibrationDeviceList.cs @@ -1,13 +1,13 @@ using Ryujinx.Core.OsHle.Ipc; using System.Collections.Generic; -namespace Ryujinx.Core.OsHle.IpcServices.Hid +namespace Ryujinx.Core.OsHle.Services.Hid { - class IActiveApplicationDeviceList : IIpcService + class IActiveApplicationDeviceList : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; public IActiveApplicationDeviceList() { diff --git a/Ryujinx.Core/OsHle/Services/Hid/IAppletResource.cs b/Ryujinx.Core/OsHle/Services/Hid/IAppletResource.cs index ef437c022..23dfd7a22 100644 --- a/Ryujinx.Core/OsHle/Services/Hid/IAppletResource.cs +++ b/Ryujinx.Core/OsHle/Services/Hid/IAppletResource.cs @@ -2,13 +2,13 @@ using Ryujinx.Core.OsHle.Handles; using Ryujinx.Core.OsHle.Ipc; using System.Collections.Generic; -namespace Ryujinx.Core.OsHle.IpcServices.Hid +namespace Ryujinx.Core.OsHle.Services.Hid { - class IAppletResource : IIpcService + class IAppletResource : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; private HSharedMem HidSharedMem; diff --git a/Ryujinx.Core/OsHle/Services/Hid/ServiceHid.cs b/Ryujinx.Core/OsHle/Services/Hid/IHidServer.cs similarity index 52% rename from Ryujinx.Core/OsHle/Services/Hid/ServiceHid.cs rename to Ryujinx.Core/OsHle/Services/Hid/IHidServer.cs index 6735c6ad2..0b401e208 100644 --- a/Ryujinx.Core/OsHle/Services/Hid/ServiceHid.cs +++ b/Ryujinx.Core/OsHle/Services/Hid/IHidServer.cs @@ -1,34 +1,40 @@ +using Ryujinx.Core.Input; using Ryujinx.Core.OsHle.Ipc; using System.Collections.Generic; -using Ryujinx.Core.Input; -using static Ryujinx.Core.OsHle.IpcServices.ObjHelper; - -namespace Ryujinx.Core.OsHle.IpcServices.Hid +namespace Ryujinx.Core.OsHle.Services.Hid { - class ServiceHid : IIpcService + class IHidServer : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; - public ServiceHid() + public IHidServer() { m_Commands = new Dictionary() { { 0, CreateAppletResource }, + { 1, ActivateDebugPad }, { 11, ActivateTouchScreen }, + { 21, ActivateMouse }, + { 31, ActivateKeyboard }, { 66, StartSixAxisSensor }, + { 79, SetGyroscopeZeroDriftMode }, { 100, SetSupportedNpadStyleSet }, { 101, GetSupportedNpadStyleSet }, { 102, SetSupportedNpadIdType }, { 103, ActivateNpad }, + { 108, GetPlayerLedPattern }, { 120, SetNpadJoyHoldType }, + { 121, GetNpadJoyHoldType }, { 122, SetNpadJoyAssignmentModeSingleByDefault }, { 123, SetNpadJoyAssignmentModeSingle }, { 124, SetNpadJoyAssignmentModeDual }, { 125, MergeSingleJoyAsDualJoy }, + { 128, SetNpadHandheldActivationMode }, { 200, GetVibrationDeviceInfo }, + { 201, SendVibrationValue }, { 203, CreateActiveVibrationDeviceList }, { 206, SendVibrationValues } }; @@ -41,9 +47,36 @@ namespace Ryujinx.Core.OsHle.IpcServices.Hid return 0; } + public long ActivateDebugPad(ServiceCtx Context) + { + Logging.Stub(LogClass.ServiceHid, "Stubbed"); + + return 0; + } + public long ActivateTouchScreen(ServiceCtx Context) { - long Unknown = Context.RequestData.ReadInt64(); + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Logging.Stub(LogClass.ServiceHid, "Stubbed"); + + return 0; + } + + public long ActivateMouse(ServiceCtx Context) + { + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Logging.Stub(LogClass.ServiceHid, "Stubbed"); + + return 0; + } + + public long ActivateKeyboard(ServiceCtx Context) + { + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Logging.Stub(LogClass.ServiceHid, "Stubbed"); return 0; } @@ -54,6 +87,19 @@ namespace Ryujinx.Core.OsHle.IpcServices.Hid long AppletResourceUserId = Context.RequestData.ReadInt64(); + Logging.Stub(LogClass.ServiceHid, "Stubbed"); + + return 0; + } + + public long SetGyroscopeZeroDriftMode(ServiceCtx Context) + { + int Handle = Context.RequestData.ReadInt32(); + int Unknown = Context.RequestData.ReadInt32(); + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Logging.Stub(LogClass.ServiceHid, "Stubbed"); + return 0; } @@ -61,6 +107,8 @@ namespace Ryujinx.Core.OsHle.IpcServices.Hid { Context.ResponseData.Write(0); + Logging.Stub(LogClass.ServiceHid, "Stubbed"); + return 0; } @@ -69,6 +117,8 @@ namespace Ryujinx.Core.OsHle.IpcServices.Hid long Unknown0 = Context.RequestData.ReadInt64(); long Unknown8 = Context.RequestData.ReadInt64(); + Logging.Stub(LogClass.ServiceHid, "Stubbed"); + return 0; } @@ -76,6 +126,8 @@ namespace Ryujinx.Core.OsHle.IpcServices.Hid { long Unknown = Context.RequestData.ReadInt64(); + Logging.Stub(LogClass.ServiceHid, "Stubbed"); + return 0; } @@ -83,6 +135,19 @@ namespace Ryujinx.Core.OsHle.IpcServices.Hid { long Unknown = Context.RequestData.ReadInt64(); + Logging.Stub(LogClass.ServiceHid, "Stubbed"); + + return 0; + } + + public long GetPlayerLedPattern(ServiceCtx Context) + { + long Unknown = Context.RequestData.ReadInt32(); + + Context.ResponseData.Write(0L); + + Logging.Stub(LogClass.ServiceHid, "Stubbed"); + return 0; } @@ -91,6 +156,8 @@ namespace Ryujinx.Core.OsHle.IpcServices.Hid long Unknown0 = Context.RequestData.ReadInt64(); long Unknown8 = Context.RequestData.ReadInt64(); + Logging.Stub(LogClass.ServiceHid, "Stubbed"); + return 0; } @@ -98,13 +165,17 @@ namespace Ryujinx.Core.OsHle.IpcServices.Hid { Context.ResponseData.Write(0L); + Logging.Stub(LogClass.ServiceHid, "Stubbed"); + return 0; } public long SetNpadJoyAssignmentModeSingleByDefault(ServiceCtx Context) { HidControllerId HidControllerId = (HidControllerId)Context.RequestData.ReadInt32(); - long AppletUserResourseId = Context.RequestData.ReadInt64(); + long AppletUserResourceId = Context.RequestData.ReadInt64(); + + Logging.Stub(LogClass.ServiceHid, "Stubbed"); return 0; } @@ -112,16 +183,20 @@ namespace Ryujinx.Core.OsHle.IpcServices.Hid public long SetNpadJoyAssignmentModeSingle(ServiceCtx Context) { HidControllerId HidControllerId = (HidControllerId)Context.RequestData.ReadInt32(); - long AppletUserResourseId = Context.RequestData.ReadInt64(); + long AppletUserResourceId = Context.RequestData.ReadInt64(); long NpadJoyDeviceType = Context.RequestData.ReadInt64(); - + + Logging.Stub(LogClass.ServiceHid, "Stubbed"); + return 0; } public long SetNpadJoyAssignmentModeDual(ServiceCtx Context) { HidControllerId HidControllerId = (HidControllerId)Context.RequestData.ReadInt32(); - long AppletUserResourseId = Context.RequestData.ReadInt64(); + long AppletUserResourceId = Context.RequestData.ReadInt64(); + + Logging.Stub(LogClass.ServiceHid, "Stubbed"); return 0; } @@ -130,7 +205,19 @@ namespace Ryujinx.Core.OsHle.IpcServices.Hid { long Unknown0 = Context.RequestData.ReadInt32(); long Unknown8 = Context.RequestData.ReadInt32(); - long AppletUserResourseId = Context.RequestData.ReadInt64(); + long AppletUserResourceId = Context.RequestData.ReadInt64(); + + Logging.Stub(LogClass.ServiceHid, "Stubbed"); + + return 0; + } + + public long SetNpadHandheldActivationMode(ServiceCtx Context) + { + long AppletUserResourceId = Context.RequestData.ReadInt64(); + long Unknown = Context.RequestData.ReadInt64(); + + Logging.Stub(LogClass.ServiceHid, "Stubbed"); return 0; } @@ -139,11 +226,29 @@ namespace Ryujinx.Core.OsHle.IpcServices.Hid { int VibrationDeviceHandle = Context.RequestData.ReadInt32(); + Logging.Stub(LogClass.ServiceHid, $"VibrationDeviceHandle = {VibrationDeviceHandle}, VibrationDeviceInfo = 0"); + Context.ResponseData.Write(0L); //VibrationDeviceInfoForIpc return 0; } + public long SendVibrationValue(ServiceCtx Context) + { + int VibrationDeviceHandle = Context.RequestData.ReadInt32(); + + int VibrationValue1 = Context.RequestData.ReadInt32(); + int VibrationValue2 = Context.RequestData.ReadInt32(); + int VibrationValue3 = Context.RequestData.ReadInt32(); + int VibrationValue4 = Context.RequestData.ReadInt32(); + + long AppletUserResourceId = Context.RequestData.ReadInt64(); + + Logging.Stub(LogClass.ServiceHid, "Stubbed"); + + return 0; + } + public long CreateActiveVibrationDeviceList(ServiceCtx Context) { MakeObject(Context, new IActiveApplicationDeviceList()); @@ -153,7 +258,9 @@ namespace Ryujinx.Core.OsHle.IpcServices.Hid public long SendVibrationValues(ServiceCtx Context) { + Logging.Stub(LogClass.ServiceHid, "Stubbed"); + return 0; } } -} \ No newline at end of file +} diff --git a/Ryujinx.Core/OsHle/Services/IIpcService.cs b/Ryujinx.Core/OsHle/Services/IIpcService.cs index eebcdfbe5..98b5c2390 100644 --- a/Ryujinx.Core/OsHle/Services/IIpcService.cs +++ b/Ryujinx.Core/OsHle/Services/IIpcService.cs @@ -1,7 +1,7 @@ using Ryujinx.Core.OsHle.Ipc; using System.Collections.Generic; -namespace Ryujinx.Core.OsHle.IpcServices +namespace Ryujinx.Core.OsHle.Services { interface IIpcService { diff --git a/Ryujinx.Core/OsHle/Services/IpcService.cs b/Ryujinx.Core/OsHle/Services/IpcService.cs new file mode 100644 index 000000000..f39adb7a3 --- /dev/null +++ b/Ryujinx.Core/OsHle/Services/IpcService.cs @@ -0,0 +1,153 @@ +using Ryujinx.Core.OsHle.Handles; +using Ryujinx.Core.OsHle.Ipc; +using System; +using System.Collections.Generic; +using System.IO; + +namespace Ryujinx.Core.OsHle.Services +{ + abstract class IpcService : IIpcService + { + public abstract IReadOnlyDictionary Commands { get; } + + private IdDictionary DomainObjects; + + private int SelfId; + + private bool IsDomain; + + public IpcService() + { + DomainObjects = new IdDictionary(); + + SelfId = -1; + } + + public int ConvertToDomain() + { + if (SelfId == -1) + { + SelfId = DomainObjects.Add(this); + } + + IsDomain = true; + + return SelfId; + } + + public void ConvertToSession() + { + IsDomain = false; + } + + public void CallMethod(ServiceCtx Context) + { + IIpcService Service = this; + + if (IsDomain) + { + int DomainWord0 = Context.RequestData.ReadInt32(); + int DomainObjId = Context.RequestData.ReadInt32(); + + long Padding = Context.RequestData.ReadInt64(); + + int DomainCmd = DomainWord0 & 0xff; + + if (DomainCmd == 1) + { + Service = GetObject(DomainObjId); + + Context.ResponseData.Write(0L); + Context.ResponseData.Write(0L); + } + else if (DomainCmd == 2) + { + Delete(DomainObjId); + + Context.ResponseData.Write(0L); + + return; + } + else + { + throw new NotImplementedException($"Domain command: {DomainCmd}"); + } + } + + long SfciMagic = Context.RequestData.ReadInt64(); + int CommandId = (int)Context.RequestData.ReadInt64(); + + if (Service.Commands.TryGetValue(CommandId, out ServiceProcessRequest ProcessRequest)) + { + Context.ResponseData.BaseStream.Seek(IsDomain ? 0x20 : 0x10, SeekOrigin.Begin); + + Logging.Trace(LogClass.KernelIpc, $"{Service.GetType().Name}: {ProcessRequest.Method.Name}"); + + long Result = ProcessRequest(Context); + + if (IsDomain) + { + foreach (int Id in Context.Response.ResponseObjIds) + { + Context.ResponseData.Write(Id); + } + + Context.ResponseData.BaseStream.Seek(0, SeekOrigin.Begin); + + Context.ResponseData.Write(Context.Response.ResponseObjIds.Count); + } + + Context.ResponseData.BaseStream.Seek(IsDomain ? 0x10 : 0, SeekOrigin.Begin); + + Context.ResponseData.Write(IpcMagic.Sfco); + Context.ResponseData.Write(Result); + } + else + { + string DbgMessage = $"{Context.Session.ServiceName} {Service.GetType().Name}: {CommandId}"; + + throw new NotImplementedException(DbgMessage); + } + } + + protected static void MakeObject(ServiceCtx Context, IpcService Obj) + { + IpcService Service = Context.Session.Service; + + if (Service.IsDomain) + { + Context.Response.ResponseObjIds.Add(Service.Add(Obj)); + } + else + { + KSession Session = new KSession(Obj, Context.Session.ServiceName); + + int Handle = Context.Process.HandleTable.OpenHandle(Session); + + Context.Response.HandleDesc = IpcHandleDesc.MakeMove(Handle); + } + } + + private int Add(IIpcService Obj) + { + return DomainObjects.Add(Obj); + } + + private bool Delete(int Id) + { + object Obj = DomainObjects.Delete(Id); + + if (Obj is IDisposable DisposableObj) + { + DisposableObj.Dispose(); + } + + return Obj != null; + } + + private IIpcService GetObject(int Id) + { + return DomainObjects.GetData(Id); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Lm/ServiceLm.cs b/Ryujinx.Core/OsHle/Services/Lm/ILogService.cs similarity index 60% rename from Ryujinx.Core/OsHle/Services/Lm/ServiceLm.cs rename to Ryujinx.Core/OsHle/Services/Lm/ILogService.cs index ca3fe35e8..3315cf4cd 100644 --- a/Ryujinx.Core/OsHle/Services/Lm/ServiceLm.cs +++ b/Ryujinx.Core/OsHle/Services/Lm/ILogService.cs @@ -1,17 +1,15 @@ using Ryujinx.Core.OsHle.Ipc; using System.Collections.Generic; -using static Ryujinx.Core.OsHle.IpcServices.ObjHelper; - -namespace Ryujinx.Core.OsHle.IpcServices.Lm +namespace Ryujinx.Core.OsHle.Services.Lm { - class ServiceLm : IIpcService + class ILogService : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; - public ServiceLm() + public ILogService() { m_Commands = new Dictionary() { @@ -21,8 +19,6 @@ namespace Ryujinx.Core.OsHle.IpcServices.Lm public long Initialize(ServiceCtx Context) { - Context.Session.Initialize(); - MakeObject(Context, new ILogger()); return 0; diff --git a/Ryujinx.Core/OsHle/Services/Lm/ILogger.cs b/Ryujinx.Core/OsHle/Services/Lm/ILogger.cs index 5ee097b6f..6d3de79b7 100644 --- a/Ryujinx.Core/OsHle/Services/Lm/ILogger.cs +++ b/Ryujinx.Core/OsHle/Services/Lm/ILogger.cs @@ -5,13 +5,13 @@ using System.Collections.Generic; using System.IO; using System.Text; -namespace Ryujinx.Core.OsHle.IpcServices.Lm +namespace Ryujinx.Core.OsHle.Services.Lm { - class ILogger : IIpcService + class ILogger : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; public ILogger() { @@ -54,7 +54,7 @@ namespace Ryujinx.Core.OsHle.IpcServices.Lm long BufferPosition = Context.Request.PtrBuff[0].Position; long BufferLen = Context.Request.PtrBuff[0].Size; - byte[] LogBuffer = AMemoryHelper.ReadBytes(Context.Memory, BufferPosition, (int)BufferLen); + byte[] LogBuffer = AMemoryHelper.ReadBytes(Context.Memory, BufferPosition, BufferLen); MemoryStream LogMessage = new MemoryStream(LogBuffer); BinaryReader bReader = new BinaryReader(LogMessage); @@ -129,11 +129,11 @@ namespace Ryujinx.Core.OsHle.IpcServices.Lm switch((Severity)iSeverity) { - case Severity.Trace: Logging.Trace(LogString); break; - case Severity.Info: Logging.Info(LogString); break; - case Severity.Warning: Logging.Warn(LogString); break; - case Severity.Error: Logging.Error(LogString); break; - case Severity.Critical: Logging.Fatal(LogString); break; + case Severity.Trace: Logging.Trace(LogClass.ServiceLm, LogString); break; + case Severity.Info: Logging.Info(LogClass.ServiceLm, LogString); break; + case Severity.Warning: Logging.Warn(LogClass.ServiceLm, LogString); break; + case Severity.Error: Logging.Error(LogClass.ServiceLm, LogString); break; + case Severity.Critical: Logging.Fatal(LogClass.ServiceLm, LogString); break; } return 0; diff --git a/Ryujinx.Core/OsHle/Services/Nifm/IGeneralService.cs b/Ryujinx.Core/OsHle/Services/Nifm/IGeneralService.cs index c31ee36b2..e40ad9f01 100644 --- a/Ryujinx.Core/OsHle/Services/Nifm/IGeneralService.cs +++ b/Ryujinx.Core/OsHle/Services/Nifm/IGeneralService.cs @@ -1,15 +1,13 @@ using Ryujinx.Core.OsHle.Ipc; using System.Collections.Generic; -using static Ryujinx.Core.OsHle.IpcServices.ObjHelper; - -namespace Ryujinx.Core.OsHle.IpcServices.Nifm +namespace Ryujinx.Core.OsHle.Services.Nifm { - class IGeneralService : IIpcService + class IGeneralService : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; public IGeneralService() { @@ -26,7 +24,7 @@ namespace Ryujinx.Core.OsHle.IpcServices.Nifm MakeObject(Context, new IRequest()); - //Todo: Stub + Logging.Stub(LogClass.ServiceNifm, "Stubbed"); return 0; } diff --git a/Ryujinx.Core/OsHle/Services/Nifm/IRequest.cs b/Ryujinx.Core/OsHle/Services/Nifm/IRequest.cs index 6110e5fbe..276183cd4 100644 --- a/Ryujinx.Core/OsHle/Services/Nifm/IRequest.cs +++ b/Ryujinx.Core/OsHle/Services/Nifm/IRequest.cs @@ -1,13 +1,17 @@ +using Ryujinx.Core.OsHle.Handles; using Ryujinx.Core.OsHle.Ipc; +using System; using System.Collections.Generic; -namespace Ryujinx.Core.OsHle.IpcServices.Nifm +namespace Ryujinx.Core.OsHle.Services.Nifm { - class IRequest : IIpcService + class IRequest : IpcService, IDisposable { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; + + private KEvent Event; public IRequest() { @@ -15,23 +19,26 @@ namespace Ryujinx.Core.OsHle.IpcServices.Nifm { { 0, GetRequestState }, { 1, GetResult }, - { 2, GetSystemEventReadableHandles } + { 2, GetSystemEventReadableHandles }, + { 3, Cancel }, + { 4, Submit }, }; + + Event = new KEvent(); } - // -> i32 public long GetRequestState(ServiceCtx Context) { Context.ResponseData.Write(0); - //Todo: Stub + Logging.Stub(LogClass.ServiceNifm, "Stubbed"); return 0; } public long GetResult(ServiceCtx Context) { - //Todo: Stub + Logging.Stub(LogClass.ServiceNifm, "Stubbed"); return 0; } @@ -39,11 +46,39 @@ namespace Ryujinx.Core.OsHle.IpcServices.Nifm //GetSystemEventReadableHandles() -> (KObject, KObject) public long GetSystemEventReadableHandles(ServiceCtx Context) { - Context.Response.HandleDesc = IpcHandleDesc.MakeMove(0xbadcafe); + //FIXME: Is this supposed to return 2 events? + int Handle = Context.Process.HandleTable.OpenHandle(Event); - //Todo: Stub + Context.Response.HandleDesc = IpcHandleDesc.MakeMove(Handle); return 0; } + + public long Cancel(ServiceCtx Context) + { + Logging.Stub(LogClass.ServiceNifm, "Stubbed"); + + return 0; + } + + public long Submit(ServiceCtx Context) + { + Logging.Stub(LogClass.ServiceNifm, "Stubbed"); + + return 0; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool Disposing) + { + if (Disposing) + { + Event.Dispose(); + } + } } } \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Nifm/ServiceNifm.cs b/Ryujinx.Core/OsHle/Services/Nifm/IStaticService.cs similarity index 65% rename from Ryujinx.Core/OsHle/Services/Nifm/ServiceNifm.cs rename to Ryujinx.Core/OsHle/Services/Nifm/IStaticService.cs index 7e183389b..2129ce43a 100644 --- a/Ryujinx.Core/OsHle/Services/Nifm/ServiceNifm.cs +++ b/Ryujinx.Core/OsHle/Services/Nifm/IStaticService.cs @@ -1,17 +1,15 @@ using Ryujinx.Core.OsHle.Ipc; using System.Collections.Generic; -using static Ryujinx.Core.OsHle.IpcServices.ObjHelper; - -namespace Ryujinx.Core.OsHle.IpcServices.Nifm +namespace Ryujinx.Core.OsHle.Services.Nifm { - class ServiceNifm : IIpcService + class IStaticService : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; - public ServiceNifm() + public IStaticService() { m_Commands = new Dictionary() { diff --git a/Ryujinx.Core/OsHle/Services/Ns/IAddOnContentManager.cs b/Ryujinx.Core/OsHle/Services/Ns/IAddOnContentManager.cs new file mode 100644 index 000000000..5c08cd628 --- /dev/null +++ b/Ryujinx.Core/OsHle/Services/Ns/IAddOnContentManager.cs @@ -0,0 +1,41 @@ +using Ryujinx.Core.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.Core.OsHle.Services.Ns +{ + class IAddOnContentManager : IpcService + { + private Dictionary m_Commands; + + public override IReadOnlyDictionary Commands => m_Commands; + + public IAddOnContentManager() + { + m_Commands = new Dictionary() + { + { 2, CountAddOnContent }, + { 3, ListAddOnContent } + }; + } + + public static long CountAddOnContent(ServiceCtx Context) + { + Context.ResponseData.Write(0); + + Logging.Stub(LogClass.ServiceNs, "Stubbed"); + + return 0; + } + + public static long ListAddOnContent(ServiceCtx Context) + { + Logging.Stub(LogClass.ServiceNs, "Stubbed"); + + //TODO: This is supposed to write a u32 array aswell. + //It's unknown what it contains. + Context.ResponseData.Write(0); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Ns/IServiceGetterInterface.cs b/Ryujinx.Core/OsHle/Services/Ns/IServiceGetterInterface.cs new file mode 100644 index 000000000..603445f99 --- /dev/null +++ b/Ryujinx.Core/OsHle/Services/Ns/IServiceGetterInterface.cs @@ -0,0 +1,20 @@ +using Ryujinx.Core.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.Core.OsHle.Services.Ns +{ + class IServiceGetterInterface : IpcService + { + private Dictionary m_Commands; + + public override IReadOnlyDictionary Commands => m_Commands; + + public IServiceGetterInterface() + { + m_Commands = new Dictionary() + { + //... + }; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Ns/ISystemUpdateInterface.cs b/Ryujinx.Core/OsHle/Services/Ns/ISystemUpdateInterface.cs new file mode 100644 index 000000000..4d9895ed9 --- /dev/null +++ b/Ryujinx.Core/OsHle/Services/Ns/ISystemUpdateInterface.cs @@ -0,0 +1,20 @@ +using Ryujinx.Core.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.Core.OsHle.Services.Ns +{ + class ISystemUpdateInterface : IpcService + { + private Dictionary m_Commands; + + public override IReadOnlyDictionary Commands => m_Commands; + + public ISystemUpdateInterface() + { + m_Commands = new Dictionary() + { + //... + }; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Ns/IVulnerabilityManagerInterface.cs b/Ryujinx.Core/OsHle/Services/Ns/IVulnerabilityManagerInterface.cs new file mode 100644 index 000000000..1091da0d3 --- /dev/null +++ b/Ryujinx.Core/OsHle/Services/Ns/IVulnerabilityManagerInterface.cs @@ -0,0 +1,20 @@ +using Ryujinx.Core.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.Core.OsHle.Services.Ns +{ + class IVulnerabilityManagerInterface : IpcService + { + private Dictionary m_Commands; + + public override IReadOnlyDictionary Commands => m_Commands; + + public IVulnerabilityManagerInterface() + { + m_Commands = new Dictionary() + { + //... + }; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Nv/ServiceNvDrv.cs b/Ryujinx.Core/OsHle/Services/Nv/INvDrvServices.cs similarity index 79% rename from Ryujinx.Core/OsHle/Services/Nv/ServiceNvDrv.cs rename to Ryujinx.Core/OsHle/Services/Nv/INvDrvServices.cs index 67ad44919..cc5f95cd3 100644 --- a/Ryujinx.Core/OsHle/Services/Nv/ServiceNvDrv.cs +++ b/Ryujinx.Core/OsHle/Services/Nv/INvDrvServices.cs @@ -1,28 +1,33 @@ using ChocolArm64.Memory; +using Ryujinx.Core.OsHle.Handles; using Ryujinx.Core.OsHle.Ipc; using Ryujinx.Core.OsHle.Utilities; using Ryujinx.Graphics.Gpu; using System; using System.Collections.Generic; +using System.IO; -namespace Ryujinx.Core.OsHle.IpcServices.NvServices +namespace Ryujinx.Core.OsHle.Services.Nv { - class ServiceNvDrv : IIpcService + class INvDrvServices : IpcService, IDisposable { private delegate long ServiceProcessIoctl(ServiceCtx Context); private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; private Dictionary<(string, int), ServiceProcessIoctl> IoctlCmds; - private IdDictionary Fds; + public static GlobalStateTable Fds { get; private set; } - private IdDictionary NvMaps; - private IdDictionary NvMapsById; + public static GlobalStateTable NvMaps { get; private set; } + public static GlobalStateTable NvMapsById { get; private set; } + public static GlobalStateTable NvMapsFb { get; private set; } - public ServiceNvDrv() + private KEvent Event; + + public INvDrvServices() { m_Commands = new Dictionary() { @@ -41,10 +46,12 @@ namespace Ryujinx.Core.OsHle.IpcServices.NvServices { ("/dev/nvhost-as-gpu", 0x4106), NvGpuAsIoctlMapBufferEx }, { ("/dev/nvhost-as-gpu", 0x4108), NvGpuAsIoctlGetVaRegions }, { ("/dev/nvhost-as-gpu", 0x4109), NvGpuAsIoctlInitializeEx }, + { ("/dev/nvhost-as-gpu", 0x4114), NvGpuAsIoctlRemap }, { ("/dev/nvhost-ctrl", 0x001b), NvHostIoctlCtrlGetConfig }, { ("/dev/nvhost-ctrl", 0x001d), NvHostIoctlCtrlEventWait }, { ("/dev/nvhost-ctrl-gpu", 0x4701), NvGpuIoctlZcullGetCtxSize }, { ("/dev/nvhost-ctrl-gpu", 0x4702), NvGpuIoctlZcullGetInfo }, + { ("/dev/nvhost-ctrl-gpu", 0x4703), NvGpuIoctlZbcSetTable }, { ("/dev/nvhost-ctrl-gpu", 0x4705), NvGpuIoctlGetCharacteristics }, { ("/dev/nvhost-ctrl-gpu", 0x4706), NvGpuIoctlGetTpcMasks }, { ("/dev/nvhost-ctrl-gpu", 0x4714), NvGpuIoctlZbcGetActiveSlotMask }, @@ -64,10 +71,16 @@ namespace Ryujinx.Core.OsHle.IpcServices.NvServices { ("/dev/nvmap", 0x010e), NvMapIocGetId }, }; - Fds = new IdDictionary(); + Event = new KEvent(); + } - NvMaps = new IdDictionary(); - NvMapsById = new IdDictionary(); + static INvDrvServices() + { + Fds = new GlobalStateTable(); + + NvMaps = new GlobalStateTable(); + NvMapsById = new GlobalStateTable(); + NvMapsFb = new GlobalStateTable(); } public long Open(ServiceCtx Context) @@ -76,7 +89,7 @@ namespace Ryujinx.Core.OsHle.IpcServices.NvServices string Name = AMemoryHelper.ReadAsciiString(Context.Memory, NamePtr); - int Fd = Fds.Add(new NvFd(Name)); + int Fd = Fds.Add(Context.Process, new NvFd(Name)); Context.ResponseData.Write(Fd); Context.ResponseData.Write(0); @@ -88,8 +101,8 @@ namespace Ryujinx.Core.OsHle.IpcServices.NvServices { int Fd = Context.RequestData.ReadInt32(); int Cmd = Context.RequestData.ReadInt32() & 0xffff; - - NvFd FdData = Fds.GetData(Fd); + + NvFd FdData = Fds.GetData(Context.Process, Fd); long Position = Context.Request.GetSendBuffPtr(); @@ -109,7 +122,7 @@ namespace Ryujinx.Core.OsHle.IpcServices.NvServices { int Fd = Context.RequestData.ReadInt32(); - Fds.Delete(Fd); + Fds.Delete(Context.Process, Fd); Context.ResponseData.Write(0); @@ -123,6 +136,8 @@ namespace Ryujinx.Core.OsHle.IpcServices.NvServices Context.ResponseData.Write(0); + NvMapsFb.Add(Context.Process, 0, new NvMapFb()); + return 0; } @@ -131,7 +146,10 @@ namespace Ryujinx.Core.OsHle.IpcServices.NvServices int Fd = Context.RequestData.ReadInt32(); int EventId = Context.RequestData.ReadInt32(); - Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(0xcafe); + //TODO: Use Fd/EventId, different channels have different events. + int Handle = Context.Process.HandleTable.OpenHandle(Event); + + Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); Context.ResponseData.Write(0); @@ -191,38 +209,43 @@ namespace Ryujinx.Core.OsHle.IpcServices.NvServices int Flags = Reader.ReadInt32(); int Kind = Reader.ReadInt32(); int Handle = Reader.ReadInt32(); - int PageSize = Reader.ReadInt32(); + int PageSize = Reader.ReadInt32(); long BuffAddr = Reader.ReadInt64(); long MapSize = Reader.ReadInt64(); long Offset = Reader.ReadInt64(); if (Handle == 0) { - //Handle 0 is valid here, but it refers to something else. - //TODO: Figure out what, for now just return success. + //This is used to store offsets for the Framebuffer(s); + NvMapFb MapFb = (NvMapFb)NvMapsFb.GetData(Context.Process, 0); + + MapFb.AddBufferOffset(BuffAddr); + return 0; } - NvMap Map = NvMaps.GetData(Handle); + NvMap Map = NvMaps.GetData(Context.Process, Handle); if (Map == null) { - Logging.Warn($"Trying to use invalid NvMap Handle {Handle}!"); - + Logging.Warn(LogClass.ServiceNv, $"Trying to use invalid NvMap Handle {Handle}!"); + return -1; //TODO: Corrent error code. } if ((Flags & 1) != 0) { - Offset = Context.Ns.Gpu.MapMemory(Map.Address, Offset, Map.Size); + Offset = Context.Ns.Gpu.MapMemory(Map.CpuAddress, Offset, Map.Size); } else { - Offset = Context.Ns.Gpu.MapMemory(Map.Address, Map.Size); + Offset = Context.Ns.Gpu.MapMemory(Map.CpuAddress, Map.Size); } Context.Memory.WriteInt64(Position + 0x20, Offset); + Map.GpuAddress = Offset; + return 0; } @@ -273,6 +296,35 @@ namespace Ryujinx.Core.OsHle.IpcServices.NvServices return 0; } + private long NvGpuAsIoctlRemap(ServiceCtx Context) + { + Context.RequestData.BaseStream.Seek(-4, SeekOrigin.Current); + + int Cmd = Context.RequestData.ReadInt32(); + + int Size = (Cmd >> 16) & 0xff; + + int Count = Size / 0x18; + + long Position = Context.Request.GetSendBuffPtr(); + + MemReader Reader = new MemReader(Context.Memory, Position); + + for (int Index = 0; Index < Count; Index++) + { + int Flags = Reader.ReadInt32(); + int Kind = Reader.ReadInt32(); + int Handle = Reader.ReadInt32(); + int Padding = Reader.ReadInt32(); + int Offset = Reader.ReadInt32(); + int Pages = Reader.ReadInt32(); + } + + //TODO + + return 0; + } + private long NvHostIoctlCtrlGetConfig(ServiceCtx Context) { long Position = Context.Request.GetSendBuffPtr(); @@ -333,6 +385,32 @@ namespace Ryujinx.Core.OsHle.IpcServices.NvServices return 0; } + private long NvGpuIoctlZbcSetTable(ServiceCtx Context) + { + long Position = Context.Request.GetSendBuffPtr(); + + MemReader Reader = new MemReader(Context.Memory, Position); + + int[] ColorDs = new int[4]; + int[] ColorL2 = new int[4]; + + ColorDs[0] = Reader.ReadInt32(); + ColorDs[1] = Reader.ReadInt32(); + ColorDs[2] = Reader.ReadInt32(); + ColorDs[3] = Reader.ReadInt32(); + + ColorL2[0] = Reader.ReadInt32(); + ColorL2[1] = Reader.ReadInt32(); + ColorL2[2] = Reader.ReadInt32(); + ColorL2[3] = Reader.ReadInt32(); + + int Depth = Reader.ReadInt32(); + int Format = Reader.ReadInt32(); + int Type = Reader.ReadInt32(); + + return 0; + } + private long NvGpuIoctlGetCharacteristics(ServiceCtx Context) { long Position = Context.Request.GetSendBuffPtr(); @@ -462,9 +540,9 @@ namespace Ryujinx.Core.OsHle.IpcServices.NvServices { byte[] Data = AMemoryHelper.ReadBytes(Context.Memory, CpuAddr, Size); - NsGpuPBEntry[] PushBuffer = NsGpuPBEntry.DecodePushBuffer(Data); + NsGpuPBEntry[] PushBuffer = NvGpuPushBuffer.Decode(Data); - Context.Ns.Gpu.ProcessPushBuffer(PushBuffer, Context.Memory); + Context.Ns.Gpu.Fifo.PushBuffer(Context.Memory, PushBuffer); } } @@ -550,13 +628,13 @@ namespace Ryujinx.Core.OsHle.IpcServices.NvServices NvMap Map = new NvMap() { Size = Size }; - Map.Handle = NvMaps.Add(Map); + Map.Handle = NvMaps.Add(Context.Process, Map); - Map.Id = NvMapsById.Add(Map); + Map.Id = NvMapsById.Add(Context.Process, Map); Context.Memory.WriteInt32(Position + 4, Map.Handle); - Logging.Info($"NvMap {Map.Id} created with size {Size:x8}!"); + Logging.Info(LogClass.ServiceNv, $"NvMap {Map.Id} created with size {Size:x8}!"); return 0; } @@ -567,12 +645,12 @@ namespace Ryujinx.Core.OsHle.IpcServices.NvServices int Id = Context.Memory.ReadInt32(Position); - NvMap Map = NvMapsById.GetData(Id); + NvMap Map = NvMapsById.GetData(Context.Process, Id); if (Map == null) { - Logging.Warn($"Trying to use invalid NvMap Id {Id}!"); - + Logging.Warn(LogClass.ServiceNv, $"Trying to use invalid NvMap Id {Id}!"); + return -1; //TODO: Corrent error code. } @@ -594,18 +672,18 @@ namespace Ryujinx.Core.OsHle.IpcServices.NvServices byte Kind = (byte)Reader.ReadInt64(); long Addr = Reader.ReadInt64(); - NvMap Map = NvMaps.GetData(Handle); + NvMap Map = NvMaps.GetData(Context.Process, Handle); if (Map == null) { - Logging.Warn($"Trying to use invalid NvMap Handle {Handle}!"); - + Logging.Warn(LogClass.ServiceNv, $"Trying to use invalid NvMap Handle {Handle}!"); + return -1; //TODO: Corrent error code. } - Map.Address = Addr; - Map.Align = Align; - Map.Kind = Kind; + Map.CpuAddress = Addr; + Map.Align = Align; + Map.Kind = Kind; return 0; } @@ -620,12 +698,12 @@ namespace Ryujinx.Core.OsHle.IpcServices.NvServices int Handle = Reader.ReadInt32(); int Padding = Reader.ReadInt32(); - NvMap Map = NvMaps.GetData(Handle); + NvMap Map = NvMaps.GetData(Context.Process, Handle); if (Map == null) { - Logging.Warn($"Trying to use invalid NvMap Handle {Handle}!"); - + Logging.Warn(LogClass.ServiceNv, $"Trying to use invalid NvMap Handle {Handle}!"); + return -1; //TODO: Corrent error code. } @@ -645,12 +723,12 @@ namespace Ryujinx.Core.OsHle.IpcServices.NvServices int Handle = Reader.ReadInt32(); int Param = Reader.ReadInt32(); - NvMap Map = NvMaps.GetData(Handle); + NvMap Map = NvMaps.GetData(Context.Process, Handle); if (Map == null) { - Logging.Warn($"Trying to use invalid NvMap Handle {Handle}!"); - + Logging.Warn(LogClass.ServiceNv, $"Trying to use invalid NvMap Handle {Handle}!"); + return -1; //TODO: Corrent error code. } @@ -675,12 +753,12 @@ namespace Ryujinx.Core.OsHle.IpcServices.NvServices int Handle = Context.Memory.ReadInt32(Position + 4); - NvMap Map = NvMaps.GetData(Handle); + NvMap Map = NvMaps.GetData(Context.Process, Handle); if (Map == null) { - Logging.Warn($"Trying to use invalid NvMap Handle {Handle}!"); - + Logging.Warn(LogClass.ServiceNv, $"Trying to use invalid NvMap Handle {Handle}!"); + return -1; //TODO: Corrent error code. } @@ -689,9 +767,17 @@ namespace Ryujinx.Core.OsHle.IpcServices.NvServices return 0; } - public NvMap GetNvMap(int Handle) + public void Dispose() { - return NvMaps.GetData(Handle); + Dispose(true); + } + + protected virtual void Dispose(bool Disposing) + { + if (Disposing) + { + Event.Dispose(); + } } } } \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvFd.cs b/Ryujinx.Core/OsHle/Services/Nv/NvFd.cs index dbce74adf..1fdbd1a17 100644 --- a/Ryujinx.Core/OsHle/Services/Nv/NvFd.cs +++ b/Ryujinx.Core/OsHle/Services/Nv/NvFd.cs @@ -1,4 +1,4 @@ -namespace Ryujinx.Core.OsHle.IpcServices.NvServices +namespace Ryujinx.Core.OsHle.Services.Nv { class NvFd { diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvMap.cs b/Ryujinx.Core/OsHle/Services/Nv/NvMap.cs index ca844f9f2..f3dd1f471 100644 --- a/Ryujinx.Core/OsHle/Services/Nv/NvMap.cs +++ b/Ryujinx.Core/OsHle/Services/Nv/NvMap.cs @@ -1,4 +1,4 @@ -namespace Ryujinx.Core.OsHle.IpcServices.NvServices +namespace Ryujinx.Core.OsHle.Services.Nv { class NvMap { @@ -7,6 +7,7 @@ namespace Ryujinx.Core.OsHle.IpcServices.NvServices public int Size; public int Align; public int Kind; - public long Address; + public long CpuAddress; + public long GpuAddress; } } \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvMapFb.cs b/Ryujinx.Core/OsHle/Services/Nv/NvMapFb.cs new file mode 100644 index 000000000..d8a47418b --- /dev/null +++ b/Ryujinx.Core/OsHle/Services/Nv/NvMapFb.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; + +namespace Ryujinx.Core.OsHle.Services.Nv +{ + class NvMapFb + { + private List BufferOffs; + + public NvMapFb() + { + BufferOffs = new List(); + } + + public void AddBufferOffset(long Offset) + { + BufferOffs.Add(Offset); + } + + public bool HasBufferOffset(int Index) + { + if ((uint)Index >= BufferOffs.Count) + { + return false; + } + + return true; + } + + public long GetBufferOffset(int Index) + { + if ((uint)Index >= BufferOffs.Count) + { + throw new ArgumentOutOfRangeException(nameof(Index)); + } + + return BufferOffs[Index]; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/ObjHelper.cs b/Ryujinx.Core/OsHle/Services/ObjHelper.cs deleted file mode 100644 index 89d986aeb..000000000 --- a/Ryujinx.Core/OsHle/Services/ObjHelper.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Ryujinx.Core.OsHle.Handles; -using Ryujinx.Core.OsHle.Ipc; - -namespace Ryujinx.Core.OsHle.IpcServices -{ - static class ObjHelper - { - public static void MakeObject(ServiceCtx Context, object Obj) - { - if (Context.Session is HDomain Dom) - { - Context.Response.ResponseObjIds.Add(Dom.Add(Obj)); - } - else - { - HSessionObj HndData = new HSessionObj(Context.Session, Obj); - - int VHandle = Context.Process.HandleTable.OpenHandle(HndData); - - Context.Response.HandleDesc = IpcHandleDesc.MakeMove(VHandle); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Pctl/IParentalControlService.cs b/Ryujinx.Core/OsHle/Services/Pctl/IParentalControlService.cs index 4eb92d31d..28b35b0a3 100644 --- a/Ryujinx.Core/OsHle/Services/Pctl/IParentalControlService.cs +++ b/Ryujinx.Core/OsHle/Services/Pctl/IParentalControlService.cs @@ -1,13 +1,13 @@ using Ryujinx.Core.OsHle.Ipc; using System.Collections.Generic; -namespace Ryujinx.Core.OsHle.IpcServices.Pctl +namespace Ryujinx.Core.OsHle.Services.Pctl { - class IParentalControlService : IIpcService + class IParentalControlService : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; public IParentalControlService() { diff --git a/Ryujinx.Core/OsHle/Services/Pctl/ServicePctl.cs b/Ryujinx.Core/OsHle/Services/Pctl/IParentalControlServiceFactory.cs similarity index 65% rename from Ryujinx.Core/OsHle/Services/Pctl/ServicePctl.cs rename to Ryujinx.Core/OsHle/Services/Pctl/IParentalControlServiceFactory.cs index 2d5e22a46..5421f1655 100644 --- a/Ryujinx.Core/OsHle/Services/Pctl/ServicePctl.cs +++ b/Ryujinx.Core/OsHle/Services/Pctl/IParentalControlServiceFactory.cs @@ -1,17 +1,15 @@ using Ryujinx.Core.OsHle.Ipc; using System.Collections.Generic; -using static Ryujinx.Core.OsHle.IpcServices.ObjHelper; - -namespace Ryujinx.Core.OsHle.IpcServices.Pctl +namespace Ryujinx.Core.OsHle.Services.Pctl { - class ServicePctl : IIpcService + class IParentalControlServiceFactory : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; - public ServicePctl() + public IParentalControlServiceFactory() { m_Commands = new Dictionary() { diff --git a/Ryujinx.Core/OsHle/Services/Pl/ServicePl.cs b/Ryujinx.Core/OsHle/Services/Pl/ISharedFontManager.cs similarity index 87% rename from Ryujinx.Core/OsHle/Services/Pl/ServicePl.cs rename to Ryujinx.Core/OsHle/Services/Pl/ISharedFontManager.cs index 9a6177993..2872577f0 100644 --- a/Ryujinx.Core/OsHle/Services/Pl/ServicePl.cs +++ b/Ryujinx.Core/OsHle/Services/Pl/ISharedFontManager.cs @@ -1,15 +1,15 @@ using Ryujinx.Core.OsHle.Ipc; using System.Collections.Generic; -namespace Ryujinx.Core.OsHle.IpcServices.Pl +namespace Ryujinx.Core.OsHle.Services.Pl { - class ServicePl : IIpcService + class ISharedFontManager : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; - public ServicePl() + public ISharedFontManager() { m_Commands = new Dictionary() { diff --git a/Ryujinx.Core/OsHle/Services/Pl/SharedFontType.cs b/Ryujinx.Core/OsHle/Services/Pl/SharedFontType.cs index 29fe02b8a..2318b1729 100644 --- a/Ryujinx.Core/OsHle/Services/Pl/SharedFontType.cs +++ b/Ryujinx.Core/OsHle/Services/Pl/SharedFontType.cs @@ -1,4 +1,4 @@ -namespace Ryujinx.Core.OsHle.IpcServices.Pl +namespace Ryujinx.Core.OsHle.Services.Pl { enum SharedFontType { diff --git a/Ryujinx.Core/OsHle/Services/Ssl/ServiceSsl.cs b/Ryujinx.Core/OsHle/Services/Prepo/IPrepoService.cs similarity index 53% rename from Ryujinx.Core/OsHle/Services/Ssl/ServiceSsl.cs rename to Ryujinx.Core/OsHle/Services/Prepo/IPrepoService.cs index 23934b140..42e438414 100644 --- a/Ryujinx.Core/OsHle/Services/Ssl/ServiceSsl.cs +++ b/Ryujinx.Core/OsHle/Services/Prepo/IPrepoService.cs @@ -1,19 +1,19 @@ using Ryujinx.Core.OsHle.Ipc; using System.Collections.Generic; -namespace Ryujinx.Core.OsHle.IpcServices.Ssl +namespace Ryujinx.Core.OsHle.Services.Prepo { - class ServiceSsl : IIpcService + class IPrepoService : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; - public ServiceSsl() + public IPrepoService() { m_Commands = new Dictionary() { - //{ 0, Function } + //... }; } } diff --git a/Ryujinx.Core/OsHle/Services/ServiceFactory.cs b/Ryujinx.Core/OsHle/Services/ServiceFactory.cs new file mode 100644 index 000000000..8e639b949 --- /dev/null +++ b/Ryujinx.Core/OsHle/Services/ServiceFactory.cs @@ -0,0 +1,146 @@ +using Ryujinx.Core.OsHle.Services.Acc; +using Ryujinx.Core.OsHle.Services.Am; +using Ryujinx.Core.OsHle.Services.Apm; +using Ryujinx.Core.OsHle.Services.Aud; +using Ryujinx.Core.OsHle.Services.Bsd; +using Ryujinx.Core.OsHle.Services.Caps; +using Ryujinx.Core.OsHle.Services.Friend; +using Ryujinx.Core.OsHle.Services.FspSrv; +using Ryujinx.Core.OsHle.Services.Hid; +using Ryujinx.Core.OsHle.Services.Lm; +using Ryujinx.Core.OsHle.Services.Ns; +using Ryujinx.Core.OsHle.Services.Nv; +using Ryujinx.Core.OsHle.Services.Pctl; +using Ryujinx.Core.OsHle.Services.Pl; +using Ryujinx.Core.OsHle.Services.Prepo; +using Ryujinx.Core.OsHle.Services.Set; +using Ryujinx.Core.OsHle.Services.Sfdnsres; +using Ryujinx.Core.OsHle.Services.Sm; +using Ryujinx.Core.OsHle.Services.Ssl; +using Ryujinx.Core.OsHle.Services.Vi; +using System; + +namespace Ryujinx.Core.OsHle.Services +{ + static class ServiceFactory + { + public static IpcService MakeService(string Name) + { + switch (Name) + { + case "acc:u0": + return new IAccountServiceForApplication(); + + case "aoc:u": + return new IAddOnContentManager(); + + case "apm": + return new IManager(); + + case "apm:p": + return new IManager(); + + case "appletAE": + return new IAllSystemAppletProxiesService(); + + case "appletOE": + return new IApplicationProxyService(); + + case "audout:u": + return new IAudioOutManager(); + + case "audren:u": + return new IAudioRendererManager(); + + case "bsd:s": + return new IClient(); + + case "bsd:u": + return new IClient(); + + case "caps:a": + return new IAlbumAccessorService(); + + case "caps:ss": + return new IScreenshotService(); + + case "friend:a": + return new IServiceCreator(); + + case "friend:u": + return new IServiceCreator(); + + case "fsp-srv": + return new IFileSystemProxy(); + + case "hid": + return new IHidServer(); + + case "lm": + return new ILogService(); + + case "nifm:u": + return new Nifm.IStaticService(); + + case "ns:ec": + return new IServiceGetterInterface(); + + case "ns:su": + return new ISystemUpdateInterface(); + + case "ns:vm": + return new IVulnerabilityManagerInterface(); + + case "nvdrv": + return new INvDrvServices(); + + case "nvdrv:a": + return new INvDrvServices(); + + case "pctl:a": + return new IParentalControlServiceFactory(); + + case "pl:u": + return new ISharedFontManager(); + + case "prepo:u": + return new IPrepoService(); + + case "set": + return new ISettingsServer(); + + case "set:sys": + return new ISystemSettingsServer(); + + case "sfdnsres": + return new IResolver(); + + case "sm:": + return new IUserInterface(); + + case "ssl": + return new ISslService(); + + case "time:a": + return new Time.IStaticService(); + + case "time:s": + return new Time.IStaticService(); + + case "time:u": + return new Time.IStaticService(); + + case "vi:m": + return new IManagerRootService(); + + case "vi:s": + return new ISystemRootService(); + + case "vi:u": + return new IApplicationRootService(); + } + + throw new NotImplementedException(Name); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Set/ISettingsServer.cs b/Ryujinx.Core/OsHle/Services/Set/ISettingsServer.cs new file mode 100644 index 000000000..ea0303f0a --- /dev/null +++ b/Ryujinx.Core/OsHle/Services/Set/ISettingsServer.cs @@ -0,0 +1,109 @@ +using Ryujinx.Core.OsHle.Ipc; +using System; +using System.Collections.Generic; +using System.IO; + +namespace Ryujinx.Core.OsHle.Services.Set +{ + class ISettingsServer : IpcService + { + private static string[] LanguageCodes = new string[] + { + "ja", + "en-US", + "fr", + "de", + "it", + "es", + "zh-CN", + "ko", + "nl", + "pt", + "ru", + "zh-TW", + "en-GB", + "fr-CA", + "es-419", + "zh-Hans", + "zh-Hant" + }; + + private Dictionary m_Commands; + + public override IReadOnlyDictionary Commands => m_Commands; + + public ISettingsServer() + { + m_Commands = new Dictionary() + { + { 0, GetLanguageCode }, + { 1, GetAvailableLanguageCodes }, + { 3, GetAvailableLanguageCodeCount } + }; + } + + public static long GetLanguageCode(ServiceCtx Context) + { + Context.ResponseData.Write(LanguageCodetoLongBE(LanguageCodes[1])); + + return 0; + } + + private static long LanguageCodetoLongBE(string LanguageCode) + { + using (MemoryStream MS = new MemoryStream()) + { + foreach (char Chr in LanguageCode) + { + MS.WriteByte((byte)Chr); + } + + for (int Offs = 0; Offs < (8 - LanguageCode.Length); Offs++) + { + MS.WriteByte(0); + } + + return BitConverter.ToInt64(MS.ToArray(), 0); + } + } + + public static long GetAvailableLanguageCodes(ServiceCtx Context) + { + long Position = Context.Request.RecvListBuff[0].Position; + short Size = Context.Request.RecvListBuff[0].Size; + + int Count = (int)((uint)Size / 8); + + if (Count > LanguageCodes.Length) + { + Count = LanguageCodes.Length; + } + + for (int Index = 0; Index < Count; Index++) + { + string LanguageCode = LanguageCodes[Index]; + + foreach (char Chr in LanguageCode) + { + Context.Memory.WriteByte(Position++, (byte)Chr); + } + + for (int Offs = 0; Offs < (8 - LanguageCode.Length); Offs++) + { + Context.Memory.WriteByte(Position++, 0); + } + } + + Context.ResponseData.Write(Count); + + return 0; + } + + public static long GetAvailableLanguageCodeCount(ServiceCtx Context) + { + Context.ResponseData.Write(LanguageCodes.Length); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Set/ISystemSettingsServer.cs b/Ryujinx.Core/OsHle/Services/Set/ISystemSettingsServer.cs new file mode 100644 index 000000000..bdf5c7b40 --- /dev/null +++ b/Ryujinx.Core/OsHle/Services/Set/ISystemSettingsServer.cs @@ -0,0 +1,87 @@ +using ChocolArm64.Memory; +using Ryujinx.Core.OsHle.Ipc; +using Ryujinx.Core.Settings; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace Ryujinx.Core.OsHle.Services.Set +{ + class ISystemSettingsServer : IpcService + { + private Dictionary m_Commands; + + public override IReadOnlyDictionary Commands => m_Commands; + + public ISystemSettingsServer() + { + m_Commands = new Dictionary() + { + { 4, GetFirmwareVersion2 }, + { 23, GetColorSetId }, + { 24, SetColorSetId } + }; + } + + public static long GetFirmwareVersion2(ServiceCtx Context) + { + long ReplyPos = Context.Request.RecvListBuff[0].Position; + long ReplySize = Context.Request.RecvListBuff[0].Size; + + byte MajorFWVersion = 0x03; + byte MinorFWVersion = 0x00; + byte MicroFWVersion = 0x00; + byte Unknown = 0x00; //Build? + + int RevisionNumber = 0x0A; + + string Platform = "NX"; + string UnknownHex = "7fbde2b0bba4d14107bf836e4643043d9f6c8e47"; + string Version = "3.0.0"; + string Build = "NintendoSDK Firmware for NX 3.0.0-10.0"; + + //http://switchbrew.org/index.php?title=System_Version_Title + using (MemoryStream MS = new MemoryStream(0x100)) + { + BinaryWriter Writer = new BinaryWriter(MS); + + Writer.Write(MajorFWVersion); + Writer.Write(MinorFWVersion); + Writer.Write(MicroFWVersion); + Writer.Write(Unknown); + + Writer.Write(RevisionNumber); + + Writer.Write(Encoding.ASCII.GetBytes(Platform)); + + MS.Seek(0x28, SeekOrigin.Begin); + Writer.Write(Encoding.ASCII.GetBytes(UnknownHex)); + + MS.Seek(0x68, SeekOrigin.Begin); + Writer.Write(Encoding.ASCII.GetBytes(Version)); + + MS.Seek(0x80, SeekOrigin.Begin); + Writer.Write(Encoding.ASCII.GetBytes(Build)); + + AMemoryHelper.WriteBytes(Context.Memory, ReplyPos, MS.ToArray()); + } + + return 0; + } + + public static long GetColorSetId(ServiceCtx Context) + { + Context.ResponseData.Write((int)Context.Ns.Settings.ThemeColor); + + return 0; + } + + public static long SetColorSetId(ServiceCtx Context) + { + int ColorSetId = Context.RequestData.ReadInt32(); + + Context.Ns.Settings.ThemeColor = (ColorSet)ColorSetId; + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Set/ServiceSet.cs b/Ryujinx.Core/OsHle/Services/Set/ServiceSet.cs deleted file mode 100644 index c60e1712a..000000000 --- a/Ryujinx.Core/OsHle/Services/Set/ServiceSet.cs +++ /dev/null @@ -1,45 +0,0 @@ -using ChocolArm64.Memory; -using Ryujinx.Core.OsHle.Ipc; -using System; -using System.Collections.Generic; - -namespace Ryujinx.Core.OsHle.IpcServices.Set -{ - class ServiceSet : IIpcService - { - private Dictionary m_Commands; - - public IReadOnlyDictionary Commands => m_Commands; - - public ServiceSet() - { - m_Commands = new Dictionary() - { - { 1, GetAvailableLanguageCodes } - }; - } - - private const int LangCodesCount = 13; - - public static long GetAvailableLanguageCodes(ServiceCtx Context) - { - int PtrBuffSize = Context.RequestData.ReadInt32(); - - if (Context.Request.RecvListBuff.Count > 0) - { - long Position = Context.Request.RecvListBuff[0].Position; - short Size = Context.Request.RecvListBuff[0].Size; - - //This should return an array of ints with values matching the LanguageCode enum. - foreach (long value in new long[] { 0L, 1L, 2L, 3L, 4L, 5L, 6L, 7L }) - { - AMemoryHelper.WriteBytes(Context.Memory, Position += 8, BitConverter.GetBytes(value)); - } - } - - Context.ResponseData.Write(LangCodesCount); - - return 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Set/ServiceSetSys.cs b/Ryujinx.Core/OsHle/Services/Set/ServiceSetSys.cs deleted file mode 100644 index dee6573d3..000000000 --- a/Ryujinx.Core/OsHle/Services/Set/ServiceSetSys.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Ryujinx.Core.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.Core.OsHle.IpcServices.Set -{ - class ServiceSetSys : IIpcService - { - private Dictionary m_Commands; - - public IReadOnlyDictionary Commands => m_Commands; - - public ServiceSetSys() - { - m_Commands = new Dictionary() - { - { 23, GetColorSetId }, - { 24, SetColorSetId } - }; - } - - public static long GetColorSetId(ServiceCtx Context) - { - Context.ResponseData.Write((int)Context.Ns.Settings.ThemeColor); - - return 0; - } - - public static long SetColorSetId(ServiceCtx Context) - { - return 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Sfdnsres/ServiceSfdnsres.cs b/Ryujinx.Core/OsHle/Services/Sfdnsres/IResolver.cs similarity index 52% rename from Ryujinx.Core/OsHle/Services/Sfdnsres/ServiceSfdnsres.cs rename to Ryujinx.Core/OsHle/Services/Sfdnsres/IResolver.cs index f110ae736..e8d48ceeb 100644 --- a/Ryujinx.Core/OsHle/Services/Sfdnsres/ServiceSfdnsres.cs +++ b/Ryujinx.Core/OsHle/Services/Sfdnsres/IResolver.cs @@ -1,19 +1,19 @@ using Ryujinx.Core.OsHle.Ipc; using System.Collections.Generic; -namespace Ryujinx.Core.OsHle.IpcServices.Sfdnsres +namespace Ryujinx.Core.OsHle.Services.Sfdnsres { - class ServiceSfdnsres : IIpcService + class IResolver : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; - public ServiceSfdnsres() + public IResolver() { m_Commands = new Dictionary() { - //{ 0, Function } + //... }; } } diff --git a/Ryujinx.Core/OsHle/Services/Sm/ServiceSm.cs b/Ryujinx.Core/OsHle/Services/Sm/IUserInterface.cs similarity index 77% rename from Ryujinx.Core/OsHle/Services/Sm/ServiceSm.cs rename to Ryujinx.Core/OsHle/Services/Sm/IUserInterface.cs index cb745e373..f7c0f1076 100644 --- a/Ryujinx.Core/OsHle/Services/Sm/ServiceSm.cs +++ b/Ryujinx.Core/OsHle/Services/Sm/IUserInterface.cs @@ -2,15 +2,17 @@ using Ryujinx.Core.OsHle.Handles; using Ryujinx.Core.OsHle.Ipc; using System.Collections.Generic; -namespace Ryujinx.Core.OsHle.IpcServices.Sm +namespace Ryujinx.Core.OsHle.Services.Sm { - class ServiceSm : IIpcService + class IUserInterface : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; - public ServiceSm() + private bool IsInitialized; + + public IUserInterface() { m_Commands = new Dictionary() { @@ -23,7 +25,7 @@ namespace Ryujinx.Core.OsHle.IpcServices.Sm public long Initialize(ServiceCtx Context) { - Context.Session.Initialize(); + IsInitialized = true; return 0; } @@ -31,7 +33,7 @@ namespace Ryujinx.Core.OsHle.IpcServices.Sm public long GetService(ServiceCtx Context) { //Only for kernel version > 3.0.0. - if (!Context.Session.IsInitialized) + if (!IsInitialized) { //return SmNotInitialized; } @@ -55,7 +57,7 @@ namespace Ryujinx.Core.OsHle.IpcServices.Sm return 0; } - HSession Session = new HSession(Context.Process.Services.GetService(Name)); + KSession Session = new KSession(ServiceFactory.MakeService(Name), Name); int Handle = Context.Process.HandleTable.OpenHandle(Session); diff --git a/Ryujinx.Core/OsHle/Services/Ns/ServiceNs.cs b/Ryujinx.Core/OsHle/Services/Ssl/ISslService.cs similarity index 54% rename from Ryujinx.Core/OsHle/Services/Ns/ServiceNs.cs rename to Ryujinx.Core/OsHle/Services/Ssl/ISslService.cs index 720baa6ec..825e33639 100644 --- a/Ryujinx.Core/OsHle/Services/Ns/ServiceNs.cs +++ b/Ryujinx.Core/OsHle/Services/Ssl/ISslService.cs @@ -1,19 +1,19 @@ using Ryujinx.Core.OsHle.Ipc; using System.Collections.Generic; -namespace Ryujinx.Core.OsHle.IpcServices.Ns +namespace Ryujinx.Core.OsHle.Services.Ssl { - class ServiceNs : IIpcService + class ISslService : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; - public ServiceNs() + public ISslService() { m_Commands = new Dictionary() { - //{ 1, Function } + //... }; } } diff --git a/Ryujinx.Core/OsHle/Services/Time/ServiceTime.cs b/Ryujinx.Core/OsHle/Services/Time/IStaticService.cs similarity index 84% rename from Ryujinx.Core/OsHle/Services/Time/ServiceTime.cs rename to Ryujinx.Core/OsHle/Services/Time/IStaticService.cs index 43f28bb80..94d9ae741 100644 --- a/Ryujinx.Core/OsHle/Services/Time/ServiceTime.cs +++ b/Ryujinx.Core/OsHle/Services/Time/IStaticService.cs @@ -1,17 +1,15 @@ using Ryujinx.Core.OsHle.Ipc; using System.Collections.Generic; -using static Ryujinx.Core.OsHle.IpcServices.ObjHelper; - -namespace Ryujinx.Core.OsHle.IpcServices.Time +namespace Ryujinx.Core.OsHle.Services.Time { - class ServiceTime : IIpcService + class IStaticService : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; - public ServiceTime() + public IStaticService() { m_Commands = new Dictionary() { diff --git a/Ryujinx.Core/OsHle/Services/Time/ISteadyClock.cs b/Ryujinx.Core/OsHle/Services/Time/ISteadyClock.cs index d20e4378a..187d7a063 100644 --- a/Ryujinx.Core/OsHle/Services/Time/ISteadyClock.cs +++ b/Ryujinx.Core/OsHle/Services/Time/ISteadyClock.cs @@ -1,13 +1,13 @@ using Ryujinx.Core.OsHle.Ipc; using System.Collections.Generic; -namespace Ryujinx.Core.OsHle.IpcServices.Time +namespace Ryujinx.Core.OsHle.Services.Time { - class ISteadyClock : IIpcService + class ISteadyClock : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; public ISteadyClock() { diff --git a/Ryujinx.Core/OsHle/Services/Time/ISystemClock.cs b/Ryujinx.Core/OsHle/Services/Time/ISystemClock.cs index 4d4493dad..9cfdcc875 100644 --- a/Ryujinx.Core/OsHle/Services/Time/ISystemClock.cs +++ b/Ryujinx.Core/OsHle/Services/Time/ISystemClock.cs @@ -2,15 +2,15 @@ using Ryujinx.Core.OsHle.Ipc; using System; using System.Collections.Generic; -namespace Ryujinx.Core.OsHle.IpcServices.Time +namespace Ryujinx.Core.OsHle.Services.Time { - class ISystemClock : IIpcService + class ISystemClock : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; - private static DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); private SystemClockType ClockType; diff --git a/Ryujinx.Core/OsHle/Services/Time/ITimeZoneService.cs b/Ryujinx.Core/OsHle/Services/Time/ITimeZoneService.cs index d220824c5..ec50c82f4 100644 --- a/Ryujinx.Core/OsHle/Services/Time/ITimeZoneService.cs +++ b/Ryujinx.Core/OsHle/Services/Time/ITimeZoneService.cs @@ -2,48 +2,49 @@ using Ryujinx.Core.OsHle.Ipc; using System; using System.Collections.Generic; -namespace Ryujinx.Core.OsHle.IpcServices.Time +namespace Ryujinx.Core.OsHle.Services.Time { - class ITimeZoneService : IIpcService + class ITimeZoneService : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; - private static DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Local); + private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); public ITimeZoneService() { m_Commands = new Dictionary() { - { 101, ToCalendarTimeWithMyRule } + { 0, GetDeviceLocationName }, + { 101, ToCalendarTimeWithMyRule } }; } - //(nn::time::PosixTime)-> (nn::time::CalendarTime, nn::time::sf::CalendarAdditionalInfo) + public long GetDeviceLocationName(ServiceCtx Context) + { + Logging.Stub(LogClass.ServiceTime, "Stubbed"); + + for (int Index = 0; Index < 0x24; Index++) + { + Context.ResponseData.Write((byte)0); + } + + return 0; + } + public long ToCalendarTimeWithMyRule(ServiceCtx Context) { long PosixTime = Context.RequestData.ReadInt64(); - Epoch = Epoch.AddSeconds(PosixTime).ToLocalTime(); + DateTime CurrentTime = Epoch.AddSeconds(PosixTime).ToLocalTime(); - /* - struct CalendarTime { - u16_le year; - u8 month; // Starts at 1 - u8 day; // Starts at 1 - u8 hour; - u8 minute; - u8 second; - INSERT_PADDING_BYTES(1); - }; - */ - Context.ResponseData.Write((short)Epoch.Year); - Context.ResponseData.Write((byte)Epoch.Month); - Context.ResponseData.Write((byte)Epoch.Day); - Context.ResponseData.Write((byte)Epoch.Hour); - Context.ResponseData.Write((byte)Epoch.Minute); - Context.ResponseData.Write((byte)Epoch.Second); + Context.ResponseData.Write((ushort)CurrentTime.Year); + Context.ResponseData.Write((byte)CurrentTime.Month); + Context.ResponseData.Write((byte)CurrentTime.Day); + Context.ResponseData.Write((byte)CurrentTime.Hour); + Context.ResponseData.Write((byte)CurrentTime.Minute); + Context.ResponseData.Write((byte)CurrentTime.Second); Context.ResponseData.Write((byte)0); /* Thanks to TuxSH @@ -57,11 +58,16 @@ namespace Ryujinx.Core.OsHle.IpcServices.Time }; }; */ - Context.ResponseData.Write((int)Epoch.DayOfWeek); - Context.ResponseData.Write(Epoch.DayOfYear); + Context.ResponseData.Write((int)CurrentTime.DayOfWeek); + + Context.ResponseData.Write(CurrentTime.DayOfYear); + + //TODO: Find out the names used. Context.ResponseData.Write(new byte[8]); - Context.ResponseData.Write(Convert.ToByte(Epoch.IsDaylightSavingTime())); - Context.ResponseData.Write(0); + + Context.ResponseData.Write((byte)(CurrentTime.IsDaylightSavingTime() ? 1 : 0)); + + Context.ResponseData.Write((int)TimeZoneInfo.Local.GetUtcOffset(CurrentTime).TotalSeconds); return 0; } diff --git a/Ryujinx.Core/OsHle/Services/Time/SystemClockType.cs b/Ryujinx.Core/OsHle/Services/Time/SystemClockType.cs index 2314942a3..7b5074ba7 100644 --- a/Ryujinx.Core/OsHle/Services/Time/SystemClockType.cs +++ b/Ryujinx.Core/OsHle/Services/Time/SystemClockType.cs @@ -1,4 +1,4 @@ -namespace Ryujinx.Core.OsHle.IpcServices.Time +namespace Ryujinx.Core.OsHle.Services.Time { enum SystemClockType { diff --git a/Ryujinx.Core/OsHle/Services/Vi/Display.cs b/Ryujinx.Core/OsHle/Services/Vi/Display.cs index ceadc3931..0dbb2eda0 100644 --- a/Ryujinx.Core/OsHle/Services/Vi/Display.cs +++ b/Ryujinx.Core/OsHle/Services/Vi/Display.cs @@ -1,4 +1,4 @@ -namespace Ryujinx.Core.OsHle.IpcServices.Vi +namespace Ryujinx.Core.OsHle.Services.Vi { class Display { diff --git a/Ryujinx.Core/OsHle/Services/Vi/GbpBuffer.cs b/Ryujinx.Core/OsHle/Services/Vi/GbpBuffer.cs index 5fe585d06..cae310544 100644 --- a/Ryujinx.Core/OsHle/Services/Vi/GbpBuffer.cs +++ b/Ryujinx.Core/OsHle/Services/Vi/GbpBuffer.cs @@ -1,6 +1,6 @@ using System.IO; -namespace Ryujinx.Core.OsHle.IpcServices.Android +namespace Ryujinx.Core.OsHle.Services.Android { struct GbpBuffer { diff --git a/Ryujinx.Core/OsHle/Services/Vi/IApplicationDisplayService.cs b/Ryujinx.Core/OsHle/Services/Vi/IApplicationDisplayService.cs index 0ff1f9099..b92dc16c4 100644 --- a/Ryujinx.Core/OsHle/Services/Vi/IApplicationDisplayService.cs +++ b/Ryujinx.Core/OsHle/Services/Vi/IApplicationDisplayService.cs @@ -1,19 +1,17 @@ using ChocolArm64.Memory; -using Ryujinx.Core.OsHle.Handles; using Ryujinx.Core.OsHle.Ipc; using System.Collections.Generic; using System.IO; -using static Ryujinx.Core.OsHle.IpcServices.Android.Parcel; -using static Ryujinx.Core.OsHle.IpcServices.ObjHelper; +using static Ryujinx.Core.OsHle.Services.Android.Parcel; -namespace Ryujinx.Core.OsHle.IpcServices.Vi +namespace Ryujinx.Core.OsHle.Services.Vi { - class IApplicationDisplayService : IIpcService + class IApplicationDisplayService : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; private IdDictionary Displays; @@ -27,6 +25,7 @@ namespace Ryujinx.Core.OsHle.IpcServices.Vi { 103, GetIndirectDisplayTransactionService }, { 1010, OpenDisplay }, { 1020, CloseDisplay }, + { 1102, GetDisplayResolution }, { 2020, OpenLayer }, { 2021, CloseLayer }, { 2030, CreateStrayLayer }, @@ -86,6 +85,16 @@ namespace Ryujinx.Core.OsHle.IpcServices.Vi return 0; } + public long GetDisplayResolution(ServiceCtx Context) + { + long DisplayId = Context.RequestData.ReadInt32(); + + Context.ResponseData.Write(1280); + Context.ResponseData.Write(720); + + return 0; + } + public long OpenLayer(ServiceCtx Context) { long LayerId = Context.RequestData.ReadInt64(); @@ -145,7 +154,7 @@ namespace Ryujinx.Core.OsHle.IpcServices.Vi { string Name = GetDisplayName(Context); - int Handle = Context.Process.HandleTable.OpenHandle(new HEvent()); + int Handle = Context.Process.HandleTable.OpenHandle(Context.Ns.Os.VsyncEvent); Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); diff --git a/Ryujinx.Core/OsHle/Services/Vi/IApplicationRootService.cs b/Ryujinx.Core/OsHle/Services/Vi/IApplicationRootService.cs new file mode 100644 index 000000000..d92b2d9d3 --- /dev/null +++ b/Ryujinx.Core/OsHle/Services/Vi/IApplicationRootService.cs @@ -0,0 +1,29 @@ +using Ryujinx.Core.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.Core.OsHle.Services.Vi +{ + class IApplicationRootService : IpcService + { + private Dictionary m_Commands; + + public override IReadOnlyDictionary Commands => m_Commands; + + public IApplicationRootService() + { + m_Commands = new Dictionary() + { + { 0, GetDisplayService } + }; + } + + public long GetDisplayService(ServiceCtx Context) + { + int ServiceType = Context.RequestData.ReadInt32(); + + MakeObject(Context, new IApplicationDisplayService()); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Vi/IHOSBinderDriver.cs b/Ryujinx.Core/OsHle/Services/Vi/IHOSBinderDriver.cs index b24a773bf..72e8ef198 100644 --- a/Ryujinx.Core/OsHle/Services/Vi/IHOSBinderDriver.cs +++ b/Ryujinx.Core/OsHle/Services/Vi/IHOSBinderDriver.cs @@ -1,17 +1,20 @@ using ChocolArm64.Memory; +using Ryujinx.Core.OsHle.Handles; using Ryujinx.Core.OsHle.Ipc; -using Ryujinx.Core.OsHle.IpcServices.Android; +using Ryujinx.Core.OsHle.Services.Android; using Ryujinx.Graphics.Gal; using System; using System.Collections.Generic; -namespace Ryujinx.Core.OsHle.IpcServices.Vi +namespace Ryujinx.Core.OsHle.Services.Vi { - class IHOSBinderDriver : IIpcService, IDisposable + class IHOSBinderDriver : IpcService, IDisposable { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; + + private KEvent ReleaseEvent; private NvFlinger Flinger; @@ -19,12 +22,15 @@ namespace Ryujinx.Core.OsHle.IpcServices.Vi { m_Commands = new Dictionary() { - { 0, TransactParcel }, - { 1, AdjustRefcount }, - { 2, GetNativeHandle } + { 0, TransactParcel }, + { 1, AdjustRefcount }, + { 2, GetNativeHandle }, + { 3, TransactParcelAuto } }; - Flinger = new NvFlinger(Renderer); + ReleaseEvent = new KEvent(); + + Flinger = new NvFlinger(Renderer, ReleaseEvent); } public long TransactParcel(ServiceCtx Context) @@ -35,7 +41,25 @@ namespace Ryujinx.Core.OsHle.IpcServices.Vi long DataPos = Context.Request.SendBuff[0].Position; long DataSize = Context.Request.SendBuff[0].Size; - byte[] Data = AMemoryHelper.ReadBytes(Context.Memory, DataPos, (int)DataSize); + byte[] Data = AMemoryHelper.ReadBytes(Context.Memory, DataPos, DataSize); + + Data = Parcel.GetParcelData(Data); + + return Flinger.ProcessParcelRequest(Context, Data, Code); + } + + //TransactParcelAuto(i32, u32, u32, buffer) -> buffer + //Buffer C (PtrBuff) and X (ReceiveListBuff) can be used here... + //But they are all null during all my tests. + public long TransactParcelAuto(ServiceCtx Context) + { + int Id = Context.RequestData.ReadInt32(); + int Code = Context.RequestData.ReadInt32(); + + long DataPos = Context.Request.SendBuff[0].Position; + long DataSize = Context.Request.SendBuff[0].Size; + + byte[] Data = AMemoryHelper.ReadBytes(Context.Memory, DataPos, DataSize); Data = Parcel.GetParcelData(Data); @@ -56,7 +80,9 @@ namespace Ryujinx.Core.OsHle.IpcServices.Vi int Id = Context.RequestData.ReadInt32(); uint Unk = Context.RequestData.ReadUInt32(); - Context.Response.HandleDesc = IpcHandleDesc.MakeMove(0xbadcafe); + int Handle = Context.Process.HandleTable.OpenHandle(ReleaseEvent); + + Context.Response.HandleDesc = IpcHandleDesc.MakeMove(Handle); return 0; } @@ -66,10 +92,12 @@ namespace Ryujinx.Core.OsHle.IpcServices.Vi Dispose(true); } - protected virtual void Dispose(bool disposing) + protected virtual void Dispose(bool Disposing) { - if (disposing) + if (Disposing) { + ReleaseEvent.Dispose(); + Flinger.Dispose(); } } diff --git a/Ryujinx.Core/OsHle/Services/Vi/IManagerDisplayService.cs b/Ryujinx.Core/OsHle/Services/Vi/IManagerDisplayService.cs index 69dbff47a..3c7925010 100644 --- a/Ryujinx.Core/OsHle/Services/Vi/IManagerDisplayService.cs +++ b/Ryujinx.Core/OsHle/Services/Vi/IManagerDisplayService.cs @@ -1,13 +1,13 @@ using Ryujinx.Core.OsHle.Ipc; using System.Collections.Generic; -namespace Ryujinx.Core.OsHle.IpcServices.Vi +namespace Ryujinx.Core.OsHle.Services.Vi { - class IManagerDisplayService : IIpcService + class IManagerDisplayService : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; public IManagerDisplayService() { diff --git a/Ryujinx.Core/OsHle/Services/Vi/ServiceVi.cs b/Ryujinx.Core/OsHle/Services/Vi/IManagerRootService.cs similarity index 60% rename from Ryujinx.Core/OsHle/Services/Vi/ServiceVi.cs rename to Ryujinx.Core/OsHle/Services/Vi/IManagerRootService.cs index 2c3dd2a3c..177e5e666 100644 --- a/Ryujinx.Core/OsHle/Services/Vi/ServiceVi.cs +++ b/Ryujinx.Core/OsHle/Services/Vi/IManagerRootService.cs @@ -1,17 +1,15 @@ using Ryujinx.Core.OsHle.Ipc; using System.Collections.Generic; -using static Ryujinx.Core.OsHle.IpcServices.ObjHelper; - -namespace Ryujinx.Core.OsHle.IpcServices.Vi +namespace Ryujinx.Core.OsHle.Services.Vi { - class ServiceVi : IIpcService + class IManagerRootService : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; - public ServiceVi() + public IManagerRootService() { m_Commands = new Dictionary() { @@ -21,7 +19,7 @@ namespace Ryujinx.Core.OsHle.IpcServices.Vi public long GetDisplayService(ServiceCtx Context) { - int Unknown = Context.RequestData.ReadInt32(); + int ServiceType = Context.RequestData.ReadInt32(); MakeObject(Context, new IApplicationDisplayService()); diff --git a/Ryujinx.Core/OsHle/Services/Vi/ISystemDisplayService.cs b/Ryujinx.Core/OsHle/Services/Vi/ISystemDisplayService.cs index d87fcbf6e..3bdeb32a6 100644 --- a/Ryujinx.Core/OsHle/Services/Vi/ISystemDisplayService.cs +++ b/Ryujinx.Core/OsHle/Services/Vi/ISystemDisplayService.cs @@ -1,19 +1,20 @@ using Ryujinx.Core.OsHle.Ipc; using System.Collections.Generic; -namespace Ryujinx.Core.OsHle.IpcServices.Vi +namespace Ryujinx.Core.OsHle.Services.Vi { - class ISystemDisplayService : IIpcService + class ISystemDisplayService : IpcService { private Dictionary m_Commands; - public IReadOnlyDictionary Commands => m_Commands; + public override IReadOnlyDictionary Commands => m_Commands; public ISystemDisplayService() { m_Commands = new Dictionary() { - { 2205, SetLayerZ } + { 2205, SetLayerZ }, + { 2207, SetLayerVisibility } }; } @@ -21,5 +22,10 @@ namespace Ryujinx.Core.OsHle.IpcServices.Vi { return 0; } + + public static long SetLayerVisibility(ServiceCtx Context) + { + return 0; + } } } \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Vi/ISystemRootService.cs b/Ryujinx.Core/OsHle/Services/Vi/ISystemRootService.cs new file mode 100644 index 000000000..47123a556 --- /dev/null +++ b/Ryujinx.Core/OsHle/Services/Vi/ISystemRootService.cs @@ -0,0 +1,29 @@ +using Ryujinx.Core.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.Core.OsHle.Services.Vi +{ + class ISystemRootService : IpcService + { + private Dictionary m_Commands; + + public override IReadOnlyDictionary Commands => m_Commands; + + public ISystemRootService() + { + m_Commands = new Dictionary() + { + { 1, GetDisplayService } + }; + } + + public long GetDisplayService(ServiceCtx Context) + { + int ServiceType = Context.RequestData.ReadInt32(); + + MakeObject(Context, new IApplicationDisplayService()); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Vi/NvFlinger.cs b/Ryujinx.Core/OsHle/Services/Vi/NvFlinger.cs index 5309dcabb..4dc019971 100644 --- a/Ryujinx.Core/OsHle/Services/Vi/NvFlinger.cs +++ b/Ryujinx.Core/OsHle/Services/Vi/NvFlinger.cs @@ -1,15 +1,16 @@ using ChocolArm64.Memory; -using Ryujinx.Core.OsHle.IpcServices.NvServices; +using Ryujinx.Core.OsHle.Handles; +using Ryujinx.Core.OsHle.Services.Nv; using Ryujinx.Graphics.Gal; +using Ryujinx.Graphics.Gpu; using System; -using System.IO; using System.Collections.Generic; +using System.IO; using System.Text; using System.Threading; +using static Ryujinx.Core.OsHle.Services.Android.Parcel; -using static Ryujinx.Core.OsHle.IpcServices.Android.Parcel; - -namespace Ryujinx.Core.OsHle.IpcServices.Android +namespace Ryujinx.Core.OsHle.Services.Android { class NvFlinger : IDisposable { @@ -17,6 +18,10 @@ namespace Ryujinx.Core.OsHle.IpcServices.Android private Dictionary<(string, int), ServiceProcessParcel> Commands; + private KEvent ReleaseEvent; + + private IGalRenderer Renderer; + private const int BufferQueueCount = 0x40; private const int BufferQueueMask = BufferQueueCount - 1; @@ -55,21 +60,13 @@ namespace Ryujinx.Core.OsHle.IpcServices.Android public GbpBuffer Data; } - private IGalRenderer Renderer; - private BufferEntry[] BufferQueue; private ManualResetEvent WaitBufferFree; - - private object RenderQueueLock; - private int RenderQueueCount; + private bool Disposed; - private bool NvFlingerDisposed; - - private bool KeepRunning; - - public NvFlinger(IGalRenderer Renderer) + public NvFlinger(IGalRenderer Renderer, KEvent ReleaseEvent) { Commands = new Dictionary<(string, int), ServiceProcessParcel>() { @@ -84,15 +81,12 @@ namespace Ryujinx.Core.OsHle.IpcServices.Android { ("android.gui.IGraphicBufferProducer", 0xe), GbpPreallocBuffer } }; - this.Renderer = Renderer; + this.Renderer = Renderer; + this.ReleaseEvent = ReleaseEvent; BufferQueue = new BufferEntry[0x40]; WaitBufferFree = new ManualResetEvent(false); - - RenderQueueLock = new object(); - - KeepRunning = true; } public long ProcessParcelRequest(ServiceCtx Context, byte[] ParcelData, int Code) @@ -118,7 +112,7 @@ namespace Ryujinx.Core.OsHle.IpcServices.Android if (Commands.TryGetValue((InterfaceName, Code), out ServiceProcessParcel ProcReq)) { - Logging.Debug($"{InterfaceName} {ProcReq.Method.Name}"); + Logging.Debug(LogClass.ServiceNv, $"{InterfaceName} {ProcReq.Method.Name}"); return ProcReq(Context, Reader); } @@ -136,7 +130,7 @@ namespace Ryujinx.Core.OsHle.IpcServices.Android using (MemoryStream MS = new MemoryStream()) { BinaryWriter Writer = new BinaryWriter(MS); - + BufferEntry Entry = BufferQueue[Slot]; int BufferCount = 1; //? @@ -240,13 +234,17 @@ namespace Ryujinx.Core.OsHle.IpcServices.Android private long GbpPreallocBuffer(ServiceCtx Context, BinaryReader ParcelReader) { int Slot = ParcelReader.ReadInt32(); - - int BufferCount = ParcelReader.ReadInt32(); - long BufferSize = ParcelReader.ReadInt64(); - BufferQueue[Slot].State = BufferState.Free; + int BufferCount = ParcelReader.ReadInt32(); - BufferQueue[Slot].Data = new GbpBuffer(ParcelReader); + if (BufferCount > 0) + { + long BufferSize = ParcelReader.ReadInt64(); + + BufferQueue[Slot].State = BufferState.Free; + + BufferQueue[Slot].Data = new GbpBuffer(ParcelReader); + } return MakeReplyParcel(Context, 0); } @@ -278,24 +276,24 @@ namespace Ryujinx.Core.OsHle.IpcServices.Android return 0; } - private unsafe void SendFrameBuffer(ServiceCtx Context, int Slot) + private void SendFrameBuffer(ServiceCtx Context, int Slot) { - int FbWidth = BufferQueue[Slot].Data.Width; - int FbHeight = BufferQueue[Slot].Data.Height; + int FbWidth = 1280; + int FbHeight = 720; - long FbSize = (uint)FbWidth * FbHeight * 4; + NvMap Map = GetNvMap(Context, Slot); - NvMap NvMap = GetNvMap(Context, Slot); + NvMapFb MapFb = (NvMapFb)INvDrvServices.NvMapsFb.GetData(Context.Process, 0); - if ((ulong)(NvMap.Address + FbSize) > AMemoryMgr.AddrSize) + long CpuAddr = Map.CpuAddress; + long GpuAddr = Map.GpuAddress; + + if (MapFb.HasBufferOffset(Slot)) { - Logging.Error($"Frame buffer address {NvMap.Address:x16} is invalid!"); + CpuAddr += MapFb.GetBufferOffset(Slot); - BufferQueue[Slot].State = BufferState.Free; - - WaitBufferFree.Set(); - - return; + //TODO: Enable once the frame buffers problems are fixed. + //GpuAddr += MapFb.GetBufferOffset(Slot); } BufferQueue[Slot].State = BufferState.Acquired; @@ -349,39 +347,28 @@ namespace Ryujinx.Core.OsHle.IpcServices.Android Rotate = -MathF.PI * 0.5f; } - lock (RenderQueueLock) - { - if (NvFlingerDisposed) - { - return; - } + Renderer.SetFrameBufferTransform(ScaleX, ScaleY, Rotate, OffsX, OffsY); - Interlocked.Increment(ref RenderQueueCount); + //TODO: Support double buffering here aswell, it is broken for GPU + //frame buffers because it seems to be completely out of sync. + if (Context.Ns.Gpu.Engine3d.IsFrameBufferPosition(GpuAddr)) + { + //Frame buffer is rendered to by the GPU, we can just + //bind the frame buffer texture, it's not necessary to read anything. + Renderer.SetFrameBuffer(GpuAddr); + } + else + { + //Frame buffer is not set on the GPU registers, in this case + //assume that the app is manually writing to it. + Texture Texture = new Texture(CpuAddr, FbWidth, FbHeight); + + byte[] Data = TextureReader.Read(Context.Memory, Texture); + + Renderer.SetFrameBuffer(Data, FbWidth, FbHeight); } - byte* Fb = (byte*)Context.Memory.Ram + NvMap.Address; - - Context.Ns.Gpu.Renderer.QueueAction(delegate() - { - Context.Ns.Gpu.Renderer.SetFrameBuffer( - Fb, - FbWidth, - FbHeight, - ScaleX, - ScaleY, - OffsX, - OffsY, - Rotate); - - BufferQueue[Slot].State = BufferState.Free; - - Interlocked.Decrement(ref RenderQueueCount); - - lock (WaitBufferFree) - { - WaitBufferFree.Set(); - } - }); + Context.Ns.Gpu.Renderer.QueueAction(() => ReleaseBuffer(Slot)); } private NvMap GetNvMap(ServiceCtx Context, int Slot) @@ -397,9 +384,19 @@ namespace Ryujinx.Core.OsHle.IpcServices.Android NvMapHandle = BitConverter.ToInt32(RawValue, 0); } - ServiceNvDrv NvDrv = (ServiceNvDrv)Context.Process.Services.GetService("nvdrv"); + return INvDrvServices.NvMaps.GetData(Context.Process, NvMapHandle); + } - return NvDrv.GetNvMap(NvMapHandle); + private void ReleaseBuffer(int Slot) + { + BufferQueue[Slot].State = BufferState.Free; + + ReleaseEvent.WaitEvent.Set(); + + lock (WaitBufferFree) + { + WaitBufferFree.Set(); + } } private int GetFreeSlotBlocking(int Width, int Height) @@ -415,9 +412,9 @@ namespace Ryujinx.Core.OsHle.IpcServices.Android break; } - Logging.Debug("Waiting for a free BufferQueue slot..."); + Logging.Debug(LogClass.ServiceNv, "Waiting for a free BufferQueue slot..."); - if (!KeepRunning) + if (Disposed) { break; } @@ -427,9 +424,9 @@ namespace Ryujinx.Core.OsHle.IpcServices.Android WaitBufferFree.WaitOne(); } - while (KeepRunning); + while (!Disposed); - Logging.Debug($"Found free BufferQueue slot {Slot}!"); + Logging.Debug(LogClass.ServiceNv, $"Found free BufferQueue slot {Slot}!"); return Slot; } @@ -467,26 +464,12 @@ namespace Ryujinx.Core.OsHle.IpcServices.Android protected virtual void Dispose(bool Disposing) { - if (Disposing && !NvFlingerDisposed) + if (Disposing && !Disposed) { - lock (RenderQueueLock) - { - NvFlingerDisposed = true; - } - - //Ensure that all pending actions was sent before - //we can safely assume that the class was disposed. - while (RenderQueueCount > 0) - { - Thread.Yield(); - } - - Renderer.ResetFrameBuffer(); + Disposed = true; lock (WaitBufferFree) { - KeepRunning = false; - WaitBufferFree.Set(); } diff --git a/Ryujinx.Core/OsHle/Services/Vi/Parcel.cs b/Ryujinx.Core/OsHle/Services/Vi/Parcel.cs index 3404c227e..1300a741d 100644 --- a/Ryujinx.Core/OsHle/Services/Vi/Parcel.cs +++ b/Ryujinx.Core/OsHle/Services/Vi/Parcel.cs @@ -1,7 +1,7 @@ using System; using System.IO; -namespace Ryujinx.Core.OsHle.IpcServices.Android +namespace Ryujinx.Core.OsHle.Services.Android { static class Parcel { diff --git a/Ryujinx.Core/OsHle/Svc/SvcThread.cs b/Ryujinx.Core/OsHle/Svc/SvcThread.cs deleted file mode 100644 index 231ee2a23..000000000 --- a/Ryujinx.Core/OsHle/Svc/SvcThread.cs +++ /dev/null @@ -1,131 +0,0 @@ -using ChocolArm64.State; -using Ryujinx.Core.OsHle.Handles; - -namespace Ryujinx.Core.OsHle.Svc -{ - partial class SvcHandler - { - private void SvcCreateThread(AThreadState ThreadState) - { - long EntryPoint = (long)ThreadState.X1; - long ArgsPtr = (long)ThreadState.X2; - long StackTop = (long)ThreadState.X3; - int Priority = (int)ThreadState.X4; - int ProcessorId = (int)ThreadState.X5; - - if (Ns.Os.TryGetProcess(ThreadState.ProcessId, out Process Process)) - { - if (ProcessorId == -2) - { - //TODO: Get this value from the NPDM file. - ProcessorId = 0; - } - - int Handle = Process.MakeThread( - EntryPoint, - StackTop, - ArgsPtr, - Priority, - ProcessorId); - - ThreadState.X0 = 0; - ThreadState.X1 = (ulong)Handle; - } - - //TODO: Error codes. - } - - private void SvcStartThread(AThreadState ThreadState) - { - int Handle = (int)ThreadState.X0; - - HThread Thread = Process.HandleTable.GetData(Handle); - - if (Thread != null) - { - Process.Scheduler.StartThread(Thread); - - ThreadState.X0 = 0; - } - - //TODO: Error codes. - } - - private void SvcExitThread(AThreadState ThreadState) - { - HThread CurrThread = Process.GetThread(ThreadState.Tpidr); - - CurrThread.Thread.StopExecution(); - } - - private void SvcSleepThread(AThreadState ThreadState) - { - ulong NanoSecs = ThreadState.X0; - - HThread CurrThread = Process.GetThread(ThreadState.Tpidr); - - if (NanoSecs == 0) - { - Process.Scheduler.Yield(CurrThread); - } - else - { - Process.Scheduler.WaitForSignal(CurrThread, (int)(NanoSecs / 1000000)); - } - } - - private void SvcGetThreadPriority(AThreadState ThreadState) - { - int Handle = (int)ThreadState.X1; - - HThread Thread = Process.HandleTable.GetData(Handle); - - if (Thread != null) - { - ThreadState.X0 = 0; - ThreadState.X1 = (ulong)Thread.Priority; - } - - //TODO: Error codes. - } - - private void SvcSetThreadPriority(AThreadState ThreadState) - { - int Handle = (int)ThreadState.X1; - int Prio = (int)ThreadState.X0; - - HThread Thread = Process.HandleTable.GetData(Handle); - - if (Thread != null) - { - Thread.Priority = Prio; - - ThreadState.X0 = 0; - } - - //TODO: Error codes. - } - - private void SvcSetThreadCoreMask(AThreadState ThreadState) - { - ThreadState.X0 = 0; - - //TODO: Error codes. - } - - private void SvcGetThreadId(AThreadState ThreadState) - { - int Handle = (int)ThreadState.X0; - - HThread Thread = Process.HandleTable.GetData(Handle); - - if (Thread != null) - { - ThreadState.X0 = 0; - ThreadState.X1 = (ulong)Thread.ThreadId; - } - - //TODO: Error codes. - } - } -} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Svc/SvcThreadSync.cs b/Ryujinx.Core/OsHle/Svc/SvcThreadSync.cs deleted file mode 100644 index 38356073e..000000000 --- a/Ryujinx.Core/OsHle/Svc/SvcThreadSync.cs +++ /dev/null @@ -1,85 +0,0 @@ -using ChocolArm64.State; -using Ryujinx.Core.OsHle.Handles; - -using static Ryujinx.Core.OsHle.ErrorCode; - -namespace Ryujinx.Core.OsHle.Svc -{ - partial class SvcHandler - { - private void SvcArbitrateLock(AThreadState ThreadState) - { - int OwnerThreadHandle = (int)ThreadState.X0; - long MutexAddress = (long)ThreadState.X1; - int RequestingThreadHandle = (int)ThreadState.X2; - - HThread RequestingThread = Process.HandleTable.GetData(RequestingThreadHandle); - - Mutex M = new Mutex(Process, MutexAddress, OwnerThreadHandle); - - M = Ns.Os.Mutexes.GetOrAdd(MutexAddress, M); - - M.WaitForLock(RequestingThread, RequestingThreadHandle); - - ThreadState.X0 = 0; - } - - private void SvcArbitrateUnlock(AThreadState ThreadState) - { - long MutexAddress = (long)ThreadState.X0; - - if (Ns.Os.Mutexes.TryGetValue(MutexAddress, out Mutex M)) - { - M.Unlock(); - } - - ThreadState.X0 = 0; - } - - private void SvcWaitProcessWideKeyAtomic(AThreadState ThreadState) - { - long MutexAddress = (long)ThreadState.X0; - long CondVarAddress = (long)ThreadState.X1; - int ThreadHandle = (int)ThreadState.X2; - long Timeout = (long)ThreadState.X3; - - HThread Thread = Process.HandleTable.GetData(ThreadHandle); - - Mutex M = new Mutex(Process, MutexAddress, ThreadHandle); - - M = Ns.Os.Mutexes.GetOrAdd(MutexAddress, M); - - M.GiveUpLock(ThreadHandle); - - CondVar Cv = new CondVar(Process, CondVarAddress, Timeout); - - Cv = Ns.Os.CondVars.GetOrAdd(CondVarAddress, Cv); - - if (!Cv.WaitForSignal(Thread)) - { - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.Timeout); - - return; - } - - M.WaitForLock(Thread, ThreadHandle); - - ThreadState.X0 = 0; - } - - private void SvcSignalProcessWideKey(AThreadState ThreadState) - { - long CondVarAddress = (long)ThreadState.X0; - int Count = (int)ThreadState.X1; - - HThread CurrThread = Process.GetThread(ThreadState.Tpidr); - - if (Ns.Os.CondVars.TryGetValue(CondVarAddress, out CondVar Cv)) - { - Cv.SetSignal(CurrThread, Count); - } - - ThreadState.X0 = 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Core/Ryujinx.Core.csproj b/Ryujinx.Core/Ryujinx.Core.csproj index 7d5ad7185..b9374af1d 100644 --- a/Ryujinx.Core/Ryujinx.Core.csproj +++ b/Ryujinx.Core/Ryujinx.Core.csproj @@ -14,6 +14,7 @@ + diff --git a/Ryujinx.Core/Settings/SetSys.cs b/Ryujinx.Core/Settings/SystemSettings.cs similarity index 72% rename from Ryujinx.Core/Settings/SetSys.cs rename to Ryujinx.Core/Settings/SystemSettings.cs index d8b6eb6ef..0f56ef3ae 100644 --- a/Ryujinx.Core/Settings/SetSys.cs +++ b/Ryujinx.Core/Settings/SystemSettings.cs @@ -1,6 +1,6 @@ namespace Ryujinx.Core.Settings { - public class SetSys + public class SystemSettings { public ColorSet ThemeColor; } diff --git a/Ryujinx.Core/Switch.cs b/Ryujinx.Core/Switch.cs index 487f3bdb9..0df8b1260 100644 --- a/Ryujinx.Core/Switch.cs +++ b/Ryujinx.Core/Switch.cs @@ -1,3 +1,4 @@ +using Ryujinx.Audio; using Ryujinx.Core.Input; using Ryujinx.Core.OsHle; using Ryujinx.Core.Settings; @@ -9,32 +10,50 @@ namespace Ryujinx.Core { public class Switch : IDisposable { - internal NsGpu Gpu { get; private set; } - internal Horizon Os { get; private set; } - internal VirtualFs VFs { get; private set; } + internal IAalOutput AudioOut { get; private set; } + + internal NsGpu Gpu { get; private set; } + + internal VirtualFileSystem VFs { get; private set; } + + public Horizon Os { get; private set; } + + public SystemSettings Settings { get; private set; } - public Hid Hid { get; private set; } - public SetSys Settings { get; private set; } public PerformanceStatistics Statistics { get; private set; } + public Hid Hid { get; private set; } + public event EventHandler Finish; - public Switch(IGalRenderer Renderer) + public Switch(IGalRenderer Renderer, IAalOutput AudioOut) { - Gpu = new NsGpu(Renderer); + if (Renderer == null) + { + throw new ArgumentNullException(nameof(Renderer)); + } - VFs = new VirtualFs(); + if (AudioOut == null) + { + throw new ArgumentNullException(nameof(AudioOut)); + } - Hid = new Hid(); + this.AudioOut = AudioOut; - Statistics = new PerformanceStatistics(); + Gpu = new NsGpu(Renderer); + + VFs = new VirtualFileSystem(); Os = new Horizon(this); + Settings = new SystemSettings(); + + Statistics = new PerformanceStatistics(); + + Hid = new Hid(); + Os.HidSharedMem.MemoryMapped += Hid.ShMemMap; Os.HidSharedMem.MemoryUnmapped += Hid.ShMemUnmap; - - Settings = new SetSys(); } public void LoadCart(string ExeFsDir, string RomFsFile = null) diff --git a/Ryujinx.Core/VirtualFs.cs b/Ryujinx.Core/VirtualFileSystem.cs similarity index 98% rename from Ryujinx.Core/VirtualFs.cs rename to Ryujinx.Core/VirtualFileSystem.cs index c0858e0ee..1c717b2ca 100644 --- a/Ryujinx.Core/VirtualFs.cs +++ b/Ryujinx.Core/VirtualFileSystem.cs @@ -3,7 +3,7 @@ using System.IO; namespace Ryujinx.Core { - class VirtualFs : IDisposable + class VirtualFileSystem : IDisposable { private const string BasePath = "RyuFs"; private const string NandPath = "nand"; diff --git a/Ryujinx.Graphics/Gal/GalBlendEquation.cs b/Ryujinx.Graphics/Gal/GalBlendEquation.cs new file mode 100644 index 000000000..7fd4ba5fa --- /dev/null +++ b/Ryujinx.Graphics/Gal/GalBlendEquation.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Graphics.Gal +{ + public enum GalBlendEquation + { + FuncAdd = 1, + FuncSubtract = 2, + FuncReverseSubtract = 3, + Min = 4, + Max = 5 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalBlendFactor.cs b/Ryujinx.Graphics/Gal/GalBlendFactor.cs new file mode 100644 index 000000000..7237c4eda --- /dev/null +++ b/Ryujinx.Graphics/Gal/GalBlendFactor.cs @@ -0,0 +1,25 @@ +namespace Ryujinx.Graphics.Gal +{ + public enum GalBlendFactor + { + Zero = 0x1, + One = 0x2, + SrcColor = 0x3, + OneMinusSrcColor = 0x4, + SrcAlpha = 0x5, + OneMinusSrcAlpha = 0x6, + DstAlpha = 0x7, + OneMinusDstAlpha = 0x8, + DstColor = 0x9, + OneMinusDstColor = 0xa, + SrcAlphaSaturate = 0xb, + Src1Color = 0x10, + OneMinusSrc1Color = 0x11, + Src1Alpha = 0x12, + OneMinusSrc1Alpha = 0x13, + ConstantColor = 0x61, + OneMinusConstantColor = 0x62, + ConstantAlpha = 0x63, + OneMinusConstantAlpha = 0x64 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalClearBufferFlags.cs b/Ryujinx.Graphics/Gal/GalClearBufferFlags.cs new file mode 100644 index 000000000..8565051ca --- /dev/null +++ b/Ryujinx.Graphics/Gal/GalClearBufferFlags.cs @@ -0,0 +1,15 @@ +using System; + +namespace Ryujinx.Graphics.Gal +{ + [Flags] + public enum GalClearBufferFlags + { + Depth = 1 << 0, + Stencil = 1 << 1, + ColorRed = 1 << 2, + ColorGreen = 1 << 3, + ColorBlue = 1 << 4, + ColorAlpha = 1 << 5 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalColorF.cs b/Ryujinx.Graphics/Gal/GalColorF.cs new file mode 100644 index 000000000..7cfb171dc --- /dev/null +++ b/Ryujinx.Graphics/Gal/GalColorF.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.Graphics.Gal +{ + public struct GalColorF + { + public float Red { get; private set; } + public float Green { get; private set; } + public float Blue { get; private set; } + public float Alpha { get; private set; } + + public GalColorF( + float Red, + float Green, + float Blue, + float Alpha) + { + this.Red = Red; + this.Green = Green; + this.Blue = Blue; + this.Alpha = Alpha; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalConsts.cs b/Ryujinx.Graphics/Gal/GalConsts.cs new file mode 100644 index 000000000..6c8857c6e --- /dev/null +++ b/Ryujinx.Graphics/Gal/GalConsts.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.Graphics.Gal +{ + public static class GalConsts + { + public const string FlipUniformName = "flip"; + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalIndexFormat.cs b/Ryujinx.Graphics/Gal/GalIndexFormat.cs new file mode 100644 index 000000000..71a50cdba --- /dev/null +++ b/Ryujinx.Graphics/Gal/GalIndexFormat.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.Gal +{ + public enum GalIndexFormat + { + Byte = 0, + Int16 = 1, + Int32 = 2 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalShaderType.cs b/Ryujinx.Graphics/Gal/GalShaderType.cs new file mode 100644 index 000000000..eb5aaf889 --- /dev/null +++ b/Ryujinx.Graphics/Gal/GalShaderType.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Graphics.Gal +{ + public enum GalShaderType + { + Vertex = 0, + TessControl = 1, + TessEvaluation = 2, + Geometry = 3, + Fragment = 4 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalTexture.cs b/Ryujinx.Graphics/Gal/GalTexture.cs new file mode 100644 index 000000000..fcf1f1ad2 --- /dev/null +++ b/Ryujinx.Graphics/Gal/GalTexture.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Graphics.Gal +{ + public struct GalTexture + { + public byte[] Data; + + public int Width; + public int Height; + + public GalTextureFormat Format; + + public GalTexture(byte[] Data, int Width, int Height, GalTextureFormat Format) + { + this.Data = Data; + this.Width = Width; + this.Height = Height; + this.Format = Format; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalTextureFilter.cs b/Ryujinx.Graphics/Gal/GalTextureFilter.cs new file mode 100644 index 000000000..8e9669f00 --- /dev/null +++ b/Ryujinx.Graphics/Gal/GalTextureFilter.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.Gal +{ + public enum GalTextureFilter + { + Nearest = 1, + Linear = 2 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalTextureFormat.cs b/Ryujinx.Graphics/Gal/GalTextureFormat.cs new file mode 100644 index 000000000..37291e18b --- /dev/null +++ b/Ryujinx.Graphics/Gal/GalTextureFormat.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.Graphics.Gal +{ + public enum GalTextureFormat + { + A8B8G8R8 = 0x8, + A1B5G5R5 = 0x14, + B5G6R5 = 0x15, + BC1 = 0x24, + BC2 = 0x25, + BC3 = 0x26, + BC4 = 0x27, + BC5 = 0x28 + } +} diff --git a/Ryujinx.Graphics/Gal/GalTextureMipFilter.cs b/Ryujinx.Graphics/Gal/GalTextureMipFilter.cs new file mode 100644 index 000000000..2123ec7d2 --- /dev/null +++ b/Ryujinx.Graphics/Gal/GalTextureMipFilter.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.Gal +{ + public enum GalTextureMipFilter + { + None = 1, + Nearest = 2, + Linear = 3 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalTextureSampler.cs b/Ryujinx.Graphics/Gal/GalTextureSampler.cs new file mode 100644 index 000000000..b9e5c7658 --- /dev/null +++ b/Ryujinx.Graphics/Gal/GalTextureSampler.cs @@ -0,0 +1,33 @@ +namespace Ryujinx.Graphics.Gal +{ + public struct GalTextureSampler + { + public GalTextureWrap AddressU { get; private set; } + public GalTextureWrap AddressV { get; private set; } + public GalTextureWrap AddressP { get; private set; } + + public GalTextureFilter MinFilter { get; private set; } + public GalTextureFilter MagFilter { get; private set; } + public GalTextureMipFilter MipFilter { get; private set; } + + public GalColorF BorderColor { get; private set; } + + public GalTextureSampler( + GalTextureWrap AddressU, + GalTextureWrap AddressV, + GalTextureWrap AddressP, + GalTextureFilter MinFilter, + GalTextureFilter MagFilter, + GalTextureMipFilter MipFilter, + GalColorF BorderColor) + { + this.AddressU = AddressU; + this.AddressV = AddressV; + this.AddressP = AddressP; + this.MinFilter = MinFilter; + this.MagFilter = MagFilter; + this.MipFilter = MipFilter; + this.BorderColor = BorderColor; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalTextureWrap.cs b/Ryujinx.Graphics/Gal/GalTextureWrap.cs new file mode 100644 index 000000000..66e531540 --- /dev/null +++ b/Ryujinx.Graphics/Gal/GalTextureWrap.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.Graphics.Gal +{ + public enum GalTextureWrap + { + Repeat = 0, + MirroredRepeat = 1, + ClampToEdge = 2, + ClampToBorder = 3, + Clamp = 4, + MirrorClampToEdge = 5, + MirrorClampToBorder = 6, + MirrorClamp = 7 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalVertexAttrib.cs b/Ryujinx.Graphics/Gal/GalVertexAttrib.cs index dc38c5934..563e624d5 100644 --- a/Ryujinx.Graphics/Gal/GalVertexAttrib.cs +++ b/Ryujinx.Graphics/Gal/GalVertexAttrib.cs @@ -2,8 +2,6 @@ namespace Ryujinx.Graphics.Gal { public struct GalVertexAttrib { - public int Index { get; private set; } - public int Buffer { get; private set; } public bool IsConst { get; private set; } public int Offset { get; private set; } @@ -13,16 +11,12 @@ namespace Ryujinx.Graphics.Gal public bool IsBgra { get; private set; } public GalVertexAttrib( - int Index, - int Buffer, bool IsConst, int Offset, GalVertexAttribSize Size, GalVertexAttribType Type, bool IsBgra) { - this.Index = Index; - this.Buffer = Buffer; this.IsConst = IsConst; this.Offset = Offset; this.Size = Size; diff --git a/Ryujinx.Graphics/Gal/IGalRenderer.cs b/Ryujinx.Graphics/Gal/IGalRenderer.cs index aa4eac4e1..af88412a2 100644 --- a/Ryujinx.Graphics/Gal/IGalRenderer.cs +++ b/Ryujinx.Graphics/Gal/IGalRenderer.cs @@ -1,29 +1,77 @@ using System; +using System.Collections.Generic; namespace Ryujinx.Graphics.Gal { public unsafe interface IGalRenderer { void QueueAction(Action ActionMthd); + void RunActions(); - void InitializeFrameBuffer(); - void ResetFrameBuffer(); void Render(); + void SetWindowSize(int Width, int Height); - void SetFrameBuffer( - byte* Fb, - int Width, - int Height, - float ScaleX, - float ScaleY, - float OffsX, - float OffsY, - float Rotate); - void SendVertexBuffer(int Index, byte[] Buffer, int Stride, GalVertexAttrib[] Attribs); + //Blend + void SetBlendEnable(bool Enable); - void SendR8G8B8A8Texture(int Index, byte[] Buffer, int Width, int Height); + void SetBlend( + GalBlendEquation Equation, + GalBlendFactor FuncSrc, + GalBlendFactor FuncDst); + + void SetBlendSeparate( + GalBlendEquation EquationRgb, + GalBlendEquation EquationAlpha, + GalBlendFactor FuncSrcRgb, + GalBlendFactor FuncDstRgb, + GalBlendFactor FuncSrcAlpha, + GalBlendFactor FuncDstAlpha); + + //Frame Buffer + void CreateFrameBuffer(long Tag, int Width, int Height); + + void BindFrameBuffer(long Tag); + + void BindFrameBufferTexture(long Tag, int Index, GalTextureSampler Sampler); + + void SetFrameBuffer(long Tag); + + void SetFrameBuffer(byte[] Data, int Width, int Height); + + void SetFrameBufferTransform(float SX, float SY, float Rotate, float TX, float TY); + + void SetViewport(int X, int Y, int Width, int Height); + + //Rasterizer + void ClearBuffers(int RtIndex, GalClearBufferFlags Flags); + + void SetVertexArray(int VbIndex, int Stride, byte[] Buffer, GalVertexAttrib[] Attribs); + + void SetIndexArray(byte[] Buffer, GalIndexFormat Format); + + void DrawArrays(int VbIndex, GalPrimitiveType PrimType); + + void DrawElements(int VbIndex, int First, GalPrimitiveType PrimType); + + //Shader + void CreateShader(long Tag, GalShaderType Type, byte[] Data); + + IEnumerable GetTextureUsage(long Tag); + + void SetConstBuffer(long Tag, int Cbuf, byte[] Data); + + void SetUniform1(string UniformName, int Value); + + void SetUniform2F(string UniformName, float X, float Y); + + void BindShader(long Tag); + + void BindProgram(); + + //Texture + void SetTextureAndSampler(int Index, GalTexture Texture, GalTextureSampler Sampler); void BindTexture(int Index); } diff --git a/Ryujinx.Graphics/Gal/OpenGL/FrameBuffer.cs b/Ryujinx.Graphics/Gal/OpenGL/FrameBuffer.cs deleted file mode 100644 index b761811f6..000000000 --- a/Ryujinx.Graphics/Gal/OpenGL/FrameBuffer.cs +++ /dev/null @@ -1,250 +0,0 @@ -using OpenTK; -using OpenTK.Graphics.OpenGL; -using System; - -namespace Ryujinx.Graphics.Gal.OpenGL -{ - unsafe class FrameBuffer - { - public int WindowWidth { get; set; } - public int WindowHeight { get; set; } - - private int VtxShaderHandle; - private int FragShaderHandle; - private int PrgShaderHandle; - - private int TexHandle; - private int TexWidth; - private int TexHeight; - - private int VaoHandle; - private int VboHandle; - - private int[] Pixels; - - private byte* FbPtr; - - private object FbPtrLock; - - public FrameBuffer(int Width, int Height) - { - if (Width < 0) - { - throw new ArgumentOutOfRangeException(nameof(Width)); - } - - if (Height < 0) - { - throw new ArgumentOutOfRangeException(nameof(Height)); - } - - FbPtrLock = new object(); - - TexWidth = Width; - TexHeight = Height; - - WindowWidth = Width; - WindowHeight = Height; - - SetupShaders(); - SetupTexture(); - SetupVertex(); - } - - private void SetupShaders() - { - VtxShaderHandle = GL.CreateShader(ShaderType.VertexShader); - FragShaderHandle = GL.CreateShader(ShaderType.FragmentShader); - - string VtxShaderSource = EmbeddedResource.GetString("GlFbVtxShader"); - string FragShaderSource = EmbeddedResource.GetString("GlFbFragShader"); - - GL.ShaderSource(VtxShaderHandle, VtxShaderSource); - GL.ShaderSource(FragShaderHandle, FragShaderSource); - GL.CompileShader(VtxShaderHandle); - GL.CompileShader(FragShaderHandle); - - PrgShaderHandle = GL.CreateProgram(); - - GL.AttachShader(PrgShaderHandle, VtxShaderHandle); - GL.AttachShader(PrgShaderHandle, FragShaderHandle); - GL.LinkProgram(PrgShaderHandle); - GL.UseProgram(PrgShaderHandle); - - int TexUniformLocation = GL.GetUniformLocation(PrgShaderHandle, "tex"); - - GL.Uniform1(TexUniformLocation, 0); - - int WindowSizeUniformLocation = GL.GetUniformLocation(PrgShaderHandle, "window_size"); - - GL.Uniform2(WindowSizeUniformLocation, new Vector2(1280.0f, 720.0f)); - } - - private void SetupTexture() - { - Pixels = new int[TexWidth * TexHeight]; - - if (TexHandle == 0) - { - TexHandle = GL.GenTexture(); - } - - GL.BindTexture(TextureTarget.Texture2D, TexHandle); - GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); - GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); - GL.TexImage2D(TextureTarget.Texture2D, - 0, - PixelInternalFormat.Rgba, - TexWidth, - TexHeight, - 0, - PixelFormat.Rgba, - PixelType.UnsignedByte, - IntPtr.Zero); - } - - private void SetupVertex() - { - VaoHandle = GL.GenVertexArray(); - VboHandle = GL.GenBuffer(); - - float[] Buffer = new float[] - { - -1, 1, 0, 0, - 1, 1, 1, 0, - -1, -1, 0, 1, - 1, -1, 1, 1 - }; - - IntPtr Length = new IntPtr(Buffer.Length * 4); - - GL.BindBuffer(BufferTarget.ArrayBuffer, VboHandle); - GL.BufferData(BufferTarget.ArrayBuffer, Length, Buffer, BufferUsageHint.StreamDraw); - GL.BindBuffer(BufferTarget.ArrayBuffer, 0); - - GL.BindVertexArray(VaoHandle); - - GL.EnableVertexAttribArray(0); - - GL.BindBuffer(BufferTarget.ArrayBuffer, VboHandle); - - GL.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, 16, 0); - - GL.EnableVertexAttribArray(1); - - GL.BindBuffer(BufferTarget.ArrayBuffer, VboHandle); - - GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, 16, 8); - - GL.BindVertexArray(0); - } - - public unsafe void Set(byte* Fb, int Width, int Height, Matrix2 Transform, Vector2 Offs) - { - if (Fb == null) - { - throw new ArgumentNullException(nameof(Fb)); - } - - if (Width < 0) - { - throw new ArgumentOutOfRangeException(nameof(Width)); - } - - if (Height < 0) - { - throw new ArgumentOutOfRangeException(nameof(Height)); - } - - lock (FbPtrLock) - { - FbPtr = Fb; - } - - if (Width != TexWidth || - Height != TexHeight) - { - TexWidth = Width; - TexHeight = Height; - - SetupTexture(); - } - - GL.UseProgram(PrgShaderHandle); - - int TransformUniformLocation = GL.GetUniformLocation(PrgShaderHandle, "transform"); - - GL.UniformMatrix2(TransformUniformLocation, false, ref Transform); - - int WindowSizeUniformLocation = GL.GetUniformLocation(PrgShaderHandle, "window_size"); - - GL.Uniform2(WindowSizeUniformLocation, new Vector2(WindowWidth, WindowHeight)); - - int OffsetUniformLocation = GL.GetUniformLocation(PrgShaderHandle, "offset"); - - GL.Uniform2(OffsetUniformLocation, Offs); - } - - public void Reset() - { - lock (FbPtrLock) - { - FbPtr = null; - } - } - - public void Render() - { - lock (FbPtrLock) - { - if (FbPtr == null) - { - return; - } - - for (int Y = 0; Y < TexHeight; Y++) - for (int X = 0; X < TexWidth; X++) - { - Pixels[X + Y * TexWidth] = *((int*)(FbPtr + GetSwizzleOffset(X, Y))); - } - } - - GL.BindTexture(TextureTarget.Texture2D, TexHandle); - GL.TexSubImage2D(TextureTarget.Texture2D, - 0, - 0, - 0, - TexWidth, - TexHeight, - PixelFormat.Rgba, - PixelType.UnsignedByte, - Pixels); - - GL.ActiveTexture(TextureUnit.Texture0); - - GL.BindVertexArray(VaoHandle); - - GL.UseProgram(PrgShaderHandle); - - GL.DrawArrays(PrimitiveType.TriangleStrip, 0, 4); - } - - private int GetSwizzleOffset(int X, int Y) - { - int Pos; - - Pos = (Y & 0x7f) >> 4; - Pos += (X >> 4) << 3; - Pos += (Y >> 7) * ((TexWidth >> 4) << 3); - Pos *= 1024; - Pos += ((Y & 0xf) >> 3) << 9; - Pos += ((X & 0xf) >> 3) << 8; - Pos += ((Y & 0x7) >> 1) << 6; - Pos += ((X & 0x7) >> 2) << 5; - Pos += ((Y & 0x1) >> 0) << 4; - Pos += ((X & 0x3) >> 0) << 2; - - return Pos; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLBlend.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLBlend.cs new file mode 100644 index 000000000..97ff8e13b --- /dev/null +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLBlend.cs @@ -0,0 +1,49 @@ +using OpenTK.Graphics.OpenGL; + +namespace Ryujinx.Graphics.Gal.OpenGL +{ + class OGLBlend + { + public void Enable() + { + GL.Enable(EnableCap.Blend); + } + + public void Disable() + { + GL.Disable(EnableCap.Blend); + } + + public void Set( + GalBlendEquation Equation, + GalBlendFactor FuncSrc, + GalBlendFactor FuncDst) + { + GL.BlendEquation( + OGLEnumConverter.GetBlendEquation(Equation)); + + GL.BlendFunc( + OGLEnumConverter.GetBlendFactorSrc(FuncSrc), + OGLEnumConverter.GetBlendFactorDst(FuncDst)); + } + + public void SetSeparate( + GalBlendEquation EquationRgb, + GalBlendEquation EquationAlpha, + GalBlendFactor FuncSrcRgb, + GalBlendFactor FuncDstRgb, + GalBlendFactor FuncSrcAlpha, + GalBlendFactor FuncDstAlpha) + { + GL.BlendEquationSeparate( + OGLEnumConverter.GetBlendEquation(EquationRgb), + OGLEnumConverter.GetBlendEquation(EquationAlpha)); + + GL.BlendFuncSeparate( + OGLEnumConverter.GetBlendFactorSrc(FuncSrcRgb), + OGLEnumConverter.GetBlendFactorDst(FuncDstRgb), + OGLEnumConverter.GetBlendFactorSrc(FuncSrcAlpha), + OGLEnumConverter.GetBlendFactorDst(FuncDstAlpha)); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs new file mode 100644 index 000000000..4cc0a0397 --- /dev/null +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs @@ -0,0 +1,198 @@ +using OpenTK.Graphics.OpenGL; +using System; + +namespace Ryujinx.Graphics.Gal.OpenGL +{ + static class OGLEnumConverter + { + public static DrawElementsType GetDrawElementsType(GalIndexFormat Format) + { + switch (Format) + { + case GalIndexFormat.Byte: return DrawElementsType.UnsignedByte; + case GalIndexFormat.Int16: return DrawElementsType.UnsignedShort; + case GalIndexFormat.Int32: return DrawElementsType.UnsignedInt; + } + + throw new ArgumentException(nameof(Format)); + } + + public static PrimitiveType GetPrimitiveType(GalPrimitiveType Type) + { + switch (Type) + { + case GalPrimitiveType.Points: return PrimitiveType.Points; + case GalPrimitiveType.Lines: return PrimitiveType.Lines; + case GalPrimitiveType.LineLoop: return PrimitiveType.LineLoop; + case GalPrimitiveType.LineStrip: return PrimitiveType.LineStrip; + case GalPrimitiveType.Triangles: return PrimitiveType.Triangles; + case GalPrimitiveType.TriangleStrip: return PrimitiveType.TriangleStrip; + case GalPrimitiveType.TriangleFan: return PrimitiveType.TriangleFan; + case GalPrimitiveType.Quads: return PrimitiveType.Quads; + case GalPrimitiveType.QuadStrip: return PrimitiveType.QuadStrip; + case GalPrimitiveType.Polygon: return PrimitiveType.Polygon; + case GalPrimitiveType.LinesAdjacency: return PrimitiveType.LinesAdjacency; + case GalPrimitiveType.LineStripAdjacency: return PrimitiveType.LineStripAdjacency; + case GalPrimitiveType.TrianglesAdjacency: return PrimitiveType.TrianglesAdjacency; + case GalPrimitiveType.TriangleStripAdjacency: return PrimitiveType.TriangleStripAdjacency; + case GalPrimitiveType.Patches: return PrimitiveType.Patches; + } + + throw new ArgumentException(nameof(Type)); + } + + public static ShaderType GetShaderType(GalShaderType Type) + { + switch (Type) + { + case GalShaderType.Vertex: return ShaderType.VertexShader; + case GalShaderType.TessControl: return ShaderType.TessControlShader; + case GalShaderType.TessEvaluation: return ShaderType.TessEvaluationShader; + case GalShaderType.Geometry: return ShaderType.GeometryShader; + case GalShaderType.Fragment: return ShaderType.FragmentShader; + } + + throw new ArgumentException(nameof(Type)); + } + + public static (PixelFormat, PixelType) GetTextureFormat(GalTextureFormat Format) + { + switch (Format) + { + case GalTextureFormat.A8B8G8R8: return (PixelFormat.Rgba, PixelType.UnsignedByte); + case GalTextureFormat.A1B5G5R5: return (PixelFormat.Rgba, PixelType.UnsignedShort5551); + case GalTextureFormat.B5G6R5: return (PixelFormat.Rgb, PixelType.UnsignedShort565); + } + + throw new NotImplementedException(Format.ToString()); + } + + public static PixelInternalFormat GetCompressedTextureFormat(GalTextureFormat Format) + { + switch (Format) + { + case GalTextureFormat.BC1: return PixelInternalFormat.CompressedRgbaS3tcDxt1Ext; + case GalTextureFormat.BC2: return PixelInternalFormat.CompressedRgbaS3tcDxt3Ext; + case GalTextureFormat.BC3: return PixelInternalFormat.CompressedRgbaS3tcDxt5Ext; + case GalTextureFormat.BC4: return PixelInternalFormat.CompressedRedRgtc1; + case GalTextureFormat.BC5: return PixelInternalFormat.CompressedRgRgtc2; + } + + throw new NotImplementedException(Format.ToString()); + } + + public static TextureWrapMode GetTextureWrapMode(GalTextureWrap Wrap) + { + switch (Wrap) + { + case GalTextureWrap.Repeat: return TextureWrapMode.Repeat; + case GalTextureWrap.MirroredRepeat: return TextureWrapMode.MirroredRepeat; + case GalTextureWrap.ClampToEdge: return TextureWrapMode.ClampToEdge; + case GalTextureWrap.ClampToBorder: return TextureWrapMode.ClampToBorder; + case GalTextureWrap.Clamp: return TextureWrapMode.Clamp; + + //TODO: Those needs extensions (and are currently wrong). + case GalTextureWrap.MirrorClampToEdge: return TextureWrapMode.ClampToEdge; + case GalTextureWrap.MirrorClampToBorder: return TextureWrapMode.ClampToBorder; + case GalTextureWrap.MirrorClamp: return TextureWrapMode.Clamp; + } + + throw new ArgumentException(nameof(Wrap)); + } + + public static TextureMinFilter GetTextureMinFilter( + GalTextureFilter MinFilter, + GalTextureMipFilter MipFilter) + { + //TODO: Mip (needs mipmap support first). + switch (MinFilter) + { + case GalTextureFilter.Nearest: return TextureMinFilter.Nearest; + case GalTextureFilter.Linear: return TextureMinFilter.Linear; + } + + throw new ArgumentException(nameof(MinFilter)); + } + + public static TextureMagFilter GetTextureMagFilter(GalTextureFilter Filter) + { + switch (Filter) + { + case GalTextureFilter.Nearest: return TextureMagFilter.Nearest; + case GalTextureFilter.Linear: return TextureMagFilter.Linear; + } + + throw new ArgumentException(nameof(Filter)); + } + + public static BlendEquationMode GetBlendEquation(GalBlendEquation BlendEquation) + { + switch (BlendEquation) + { + case GalBlendEquation.FuncAdd: return BlendEquationMode.FuncAdd; + case GalBlendEquation.FuncSubtract: return BlendEquationMode.FuncSubtract; + case GalBlendEquation.FuncReverseSubtract: return BlendEquationMode.FuncReverseSubtract; + case GalBlendEquation.Min: return BlendEquationMode.Min; + case GalBlendEquation.Max: return BlendEquationMode.Max; + } + + throw new ArgumentException(nameof(BlendEquation)); + } + + public static BlendingFactorSrc GetBlendFactorSrc(GalBlendFactor BlendFactor) + { + switch (BlendFactor) + { + case GalBlendFactor.Zero: return BlendingFactorSrc.Zero; + case GalBlendFactor.One: return BlendingFactorSrc.One; + case GalBlendFactor.SrcColor: return BlendingFactorSrc.SrcColor; + case GalBlendFactor.OneMinusSrcColor: return BlendingFactorSrc.OneMinusSrcColor; + case GalBlendFactor.DstColor: return BlendingFactorSrc.DstColor; + case GalBlendFactor.OneMinusDstColor: return BlendingFactorSrc.OneMinusDstColor; + case GalBlendFactor.SrcAlpha: return BlendingFactorSrc.SrcAlpha; + case GalBlendFactor.OneMinusSrcAlpha: return BlendingFactorSrc.OneMinusSrcAlpha; + case GalBlendFactor.DstAlpha: return BlendingFactorSrc.DstAlpha; + case GalBlendFactor.OneMinusDstAlpha: return BlendingFactorSrc.OneMinusDstAlpha; + case GalBlendFactor.ConstantColor: return BlendingFactorSrc.ConstantColor; + case GalBlendFactor.OneMinusConstantColor: return BlendingFactorSrc.OneMinusConstantColor; + case GalBlendFactor.ConstantAlpha: return BlendingFactorSrc.ConstantAlpha; + case GalBlendFactor.OneMinusConstantAlpha: return BlendingFactorSrc.OneMinusConstantAlpha; + case GalBlendFactor.SrcAlphaSaturate: return BlendingFactorSrc.SrcAlphaSaturate; + case GalBlendFactor.Src1Color: return BlendingFactorSrc.Src1Color; + case GalBlendFactor.OneMinusSrc1Color: return BlendingFactorSrc.OneMinusSrc1Color; + case GalBlendFactor.Src1Alpha: return BlendingFactorSrc.Src1Alpha; + case GalBlendFactor.OneMinusSrc1Alpha: return BlendingFactorSrc.OneMinusSrc1Alpha; + } + + throw new ArgumentException(nameof(BlendFactor)); + } + + public static BlendingFactorDest GetBlendFactorDst(GalBlendFactor BlendFactor) + { + switch (BlendFactor) + { + case GalBlendFactor.Zero: return BlendingFactorDest.Zero; + case GalBlendFactor.One: return BlendingFactorDest.One; + case GalBlendFactor.SrcColor: return BlendingFactorDest.SrcColor; + case GalBlendFactor.OneMinusSrcColor: return BlendingFactorDest.OneMinusSrcColor; + case GalBlendFactor.DstColor: return BlendingFactorDest.DstColor; + case GalBlendFactor.OneMinusDstColor: return BlendingFactorDest.OneMinusDstColor; + case GalBlendFactor.SrcAlpha: return BlendingFactorDest.SrcAlpha; + case GalBlendFactor.OneMinusSrcAlpha: return BlendingFactorDest.OneMinusSrcAlpha; + case GalBlendFactor.DstAlpha: return BlendingFactorDest.DstAlpha; + case GalBlendFactor.OneMinusDstAlpha: return BlendingFactorDest.OneMinusDstAlpha; + case GalBlendFactor.ConstantColor: return BlendingFactorDest.ConstantColor; + case GalBlendFactor.OneMinusConstantColor: return BlendingFactorDest.OneMinusConstantColor; + case GalBlendFactor.ConstantAlpha: return BlendingFactorDest.ConstantAlpha; + case GalBlendFactor.OneMinusConstantAlpha: return BlendingFactorDest.OneMinusConstantAlpha; + case GalBlendFactor.SrcAlphaSaturate: return BlendingFactorDest.SrcAlphaSaturate; + case GalBlendFactor.Src1Color: return BlendingFactorDest.Src1Color; + case GalBlendFactor.OneMinusSrc1Color: return BlendingFactorDest.OneMinusSrc1Color; + case GalBlendFactor.Src1Alpha: return BlendingFactorDest.Src1Alpha; + case GalBlendFactor.OneMinusSrc1Alpha: return BlendingFactorDest.OneMinusSrc1Alpha; + } + + throw new ArgumentException(nameof(BlendFactor)); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLFrameBuffer.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLFrameBuffer.cs new file mode 100644 index 000000000..05a7288a8 --- /dev/null +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLFrameBuffer.cs @@ -0,0 +1,391 @@ +using OpenTK; +using OpenTK.Graphics.OpenGL; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Gal.OpenGL +{ + class OGLFrameBuffer + { + private struct Rect + { + public int X { get; private set; } + public int Y { get; private set; } + public int Width { get; private set; } + public int Height { get; private set; } + + public Rect(int X, int Y, int Width, int Height) + { + this.X = X; + this.Y = Y; + this.Width = Width; + this.Height = Height; + } + } + + private class FrameBuffer + { + public int Width { get; set; } + public int Height { get; set; } + + public int Handle { get; private set; } + public int RbHandle { get; private set; } + public int TexHandle { get; private set; } + + public FrameBuffer(int Width, int Height) + { + this.Width = Width; + this.Height = Height; + + Handle = GL.GenFramebuffer(); + RbHandle = GL.GenRenderbuffer(); + TexHandle = GL.GenTexture(); + } + } + + private struct ShaderProgram + { + public int Handle; + public int VpHandle; + public int FpHandle; + } + + private Dictionary Fbs; + + private ShaderProgram Shader; + + private Rect Viewport; + private Rect Window; + + private bool IsInitialized; + + private int RawFbTexWidth; + private int RawFbTexHeight; + private int RawFbTexHandle; + + private int CurrFbHandle; + private int CurrTexHandle; + + private int VaoHandle; + private int VboHandle; + + public OGLFrameBuffer() + { + Fbs = new Dictionary(); + + Shader = new ShaderProgram(); + } + + public void Create(long Tag, int Width, int Height) + { + //TODO: We should either use the original frame buffer size, + //or just remove the Width/Height arguments. + Width = Window.Width; + Height = Window.Height; + + if (Fbs.TryGetValue(Tag, out FrameBuffer Fb)) + { + if (Fb.Width != Width || + Fb.Height != Height) + { + SetupTexture(Fb.TexHandle, Width, Height); + + Fb.Width = Width; + Fb.Height = Height; + } + + return; + } + + Fb = new FrameBuffer(Width, Height); + + SetupTexture(Fb.TexHandle, Width, Height); + + GL.BindFramebuffer(FramebufferTarget.Framebuffer, Fb.Handle); + + GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, Fb.RbHandle); + + GL.RenderbufferStorage( + RenderbufferTarget.Renderbuffer, + RenderbufferStorage.Depth24Stencil8, + Width, + Height); + + GL.FramebufferRenderbuffer( + FramebufferTarget.Framebuffer, + FramebufferAttachment.DepthStencilAttachment, + RenderbufferTarget.Renderbuffer, + Fb.RbHandle); + + GL.FramebufferTexture( + FramebufferTarget.Framebuffer, + FramebufferAttachment.ColorAttachment0, + Fb.TexHandle, + 0); + + GL.DrawBuffer(DrawBufferMode.ColorAttachment0); + + GL.Viewport(0, 0, Width, Height); + + Fbs.Add(Tag, Fb); + } + + public void Bind(long Tag) + { + if (Fbs.TryGetValue(Tag, out FrameBuffer Fb)) + { + GL.BindFramebuffer(FramebufferTarget.Framebuffer, Fb.Handle); + + CurrFbHandle = Fb.Handle; + } + } + + public void BindTexture(long Tag, int Index) + { + if (Fbs.TryGetValue(Tag, out FrameBuffer Fb)) + { + GL.ActiveTexture(TextureUnit.Texture0 + Index); + + GL.BindTexture(TextureTarget.Texture2D, Fb.TexHandle); + } + } + + public void Set(long Tag) + { + if (Fbs.TryGetValue(Tag, out FrameBuffer Fb)) + { + CurrTexHandle = Fb.TexHandle; + } + } + + public void Set(byte[] Data, int Width, int Height) + { + if (RawFbTexHandle == 0) + { + RawFbTexHandle = GL.GenTexture(); + } + + if (RawFbTexWidth != Width || + RawFbTexHeight != Height) + { + SetupTexture(RawFbTexHandle, Width, Height); + + RawFbTexWidth = Width; + RawFbTexHeight = Height; + } + + GL.ActiveTexture(TextureUnit.Texture0); + + GL.BindTexture(TextureTarget.Texture2D, RawFbTexHandle); + + (PixelFormat Format, PixelType Type) = OGLEnumConverter.GetTextureFormat(GalTextureFormat.A8B8G8R8); + + GL.TexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, Width, Height, Format, Type, Data); + + CurrTexHandle = RawFbTexHandle; + } + + public void SetTransform(Matrix2 Transform, Vector2 Offs) + { + EnsureInitialized(); + + int CurrentProgram = GL.GetInteger(GetPName.CurrentProgram); + + GL.UseProgram(Shader.Handle); + + int TransformUniformLocation = GL.GetUniformLocation(Shader.Handle, "transform"); + + GL.UniformMatrix2(TransformUniformLocation, false, ref Transform); + + int OffsetUniformLocation = GL.GetUniformLocation(Shader.Handle, "offset"); + + GL.Uniform2(OffsetUniformLocation, ref Offs); + + GL.UseProgram(CurrentProgram); + } + + public void SetWindowSize(int Width, int Height) + { + int CurrentProgram = GL.GetInteger(GetPName.CurrentProgram); + + GL.UseProgram(Shader.Handle); + + int WindowSizeUniformLocation = GL.GetUniformLocation(Shader.Handle, "window_size"); + + GL.Uniform2(WindowSizeUniformLocation, new Vector2(Width, Height)); + + GL.UseProgram(CurrentProgram); + + Window = new Rect(0, 0, Width, Height); + } + + public void SetViewport(int X, int Y, int Width, int Height) + { + Viewport = new Rect(X, Y, Width, Height); + + //TODO + } + + public void Render() + { + if (CurrTexHandle != 0) + { + EnsureInitialized(); + + bool AlphaBlendEnable = GL.GetInteger(GetPName.Blend) != 0; + + GL.Disable(EnableCap.Blend); + + GL.ActiveTexture(TextureUnit.Texture0); + + GL.BindTexture(TextureTarget.Texture2D, CurrTexHandle); + + int CurrentProgram = GL.GetInteger(GetPName.CurrentProgram); + + GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0); + + SetViewport(Window); + + GL.Clear( + ClearBufferMask.ColorBufferBit | + ClearBufferMask.DepthBufferBit); + + GL.BindVertexArray(VaoHandle); + + GL.UseProgram(Shader.Handle); + + GL.DrawArrays(PrimitiveType.TriangleStrip, 0, 4); + + //Restore the original state. + GL.BindFramebuffer(FramebufferTarget.Framebuffer, CurrFbHandle); + + GL.UseProgram(CurrentProgram); + + if (AlphaBlendEnable) + { + GL.Enable(EnableCap.Blend); + } + + //GL.Viewport(0, 0, 1280, 720); + } + } + + private void SetViewport(Rect Viewport) + { + GL.Viewport( + Viewport.X, + Viewport.Y, + Viewport.Width, + Viewport.Height); + } + + private void EnsureInitialized() + { + if (!IsInitialized) + { + IsInitialized = true; + + SetupShader(); + SetupVertex(); + } + } + + private void SetupShader() + { + Shader.VpHandle = GL.CreateShader(ShaderType.VertexShader); + Shader.FpHandle = GL.CreateShader(ShaderType.FragmentShader); + + string VpSource = EmbeddedResource.GetString("GlFbVtxShader"); + string FpSource = EmbeddedResource.GetString("GlFbFragShader"); + + GL.ShaderSource(Shader.VpHandle, VpSource); + GL.ShaderSource(Shader.FpHandle, FpSource); + GL.CompileShader(Shader.VpHandle); + GL.CompileShader(Shader.FpHandle); + + Shader.Handle = GL.CreateProgram(); + + GL.AttachShader(Shader.Handle, Shader.VpHandle); + GL.AttachShader(Shader.Handle, Shader.FpHandle); + GL.LinkProgram(Shader.Handle); + GL.UseProgram(Shader.Handle); + + Matrix2 Transform = Matrix2.Identity; + + int TexUniformLocation = GL.GetUniformLocation(Shader.Handle, "tex"); + + GL.Uniform1(TexUniformLocation, 0); + + int WindowSizeUniformLocation = GL.GetUniformLocation(Shader.Handle, "window_size"); + + GL.Uniform2(WindowSizeUniformLocation, new Vector2(1280.0f, 720.0f)); + + int TransformUniformLocation = GL.GetUniformLocation(Shader.Handle, "transform"); + + GL.UniformMatrix2(TransformUniformLocation, false, ref Transform); + } + + private void SetupVertex() + { + VaoHandle = GL.GenVertexArray(); + VboHandle = GL.GenBuffer(); + + float[] Buffer = new float[] + { + -1, 1, 0, 0, + 1, 1, 1, 0, + -1, -1, 0, 1, + 1, -1, 1, 1 + }; + + IntPtr Length = new IntPtr(Buffer.Length * 4); + + GL.BindBuffer(BufferTarget.ArrayBuffer, VboHandle); + GL.BufferData(BufferTarget.ArrayBuffer, Length, Buffer, BufferUsageHint.StreamDraw); + GL.BindBuffer(BufferTarget.ArrayBuffer, 0); + + GL.BindVertexArray(VaoHandle); + + GL.EnableVertexAttribArray(0); + + GL.BindBuffer(BufferTarget.ArrayBuffer, VboHandle); + + GL.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, 16, 0); + + GL.EnableVertexAttribArray(1); + + GL.BindBuffer(BufferTarget.ArrayBuffer, VboHandle); + + GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, 16, 8); + } + + private void SetupTexture(int Handle, int Width, int Height) + { + GL.BindTexture(TextureTarget.Texture2D, Handle); + + const int MinFilter = (int)TextureMinFilter.Linear; + const int MagFilter = (int)TextureMagFilter.Linear; + + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, MinFilter); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, MagFilter); + + (PixelFormat Format, PixelType Type) = OGLEnumConverter.GetTextureFormat(GalTextureFormat.A8B8G8R8); + + const PixelInternalFormat InternalFmt = PixelInternalFormat.Rgba; + + const int Level = 0; + const int Border = 0; + + GL.TexImage2D( + TextureTarget.Texture2D, + Level, + InternalFmt, + Width, + Height, + Border, + Format, + Type, + IntPtr.Zero); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs new file mode 100644 index 000000000..9e0d45233 --- /dev/null +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs @@ -0,0 +1,231 @@ +using OpenTK.Graphics.OpenGL; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Gal.OpenGL +{ + class OGLRasterizer + { + private static Dictionary AttribElements = + new Dictionary() + { + { GalVertexAttribSize._32_32_32_32, 4 }, + { GalVertexAttribSize._32_32_32, 3 }, + { GalVertexAttribSize._16_16_16_16, 4 }, + { GalVertexAttribSize._32_32, 2 }, + { GalVertexAttribSize._16_16_16, 3 }, + { GalVertexAttribSize._8_8_8_8, 4 }, + { GalVertexAttribSize._16_16, 2 }, + { GalVertexAttribSize._32, 1 }, + { GalVertexAttribSize._8_8_8, 3 }, + { GalVertexAttribSize._8_8, 2 }, + { GalVertexAttribSize._16, 1 }, + { GalVertexAttribSize._8, 1 }, + { GalVertexAttribSize._10_10_10_2, 4 }, + { GalVertexAttribSize._11_11_10, 3 } + }; + + private static Dictionary AttribTypes = + new Dictionary() + { + { GalVertexAttribSize._32_32_32_32, VertexAttribPointerType.Int }, + { GalVertexAttribSize._32_32_32, VertexAttribPointerType.Int }, + { GalVertexAttribSize._16_16_16_16, VertexAttribPointerType.Short }, + { GalVertexAttribSize._32_32, VertexAttribPointerType.Int }, + { GalVertexAttribSize._16_16_16, VertexAttribPointerType.Short }, + { GalVertexAttribSize._8_8_8_8, VertexAttribPointerType.Byte }, + { GalVertexAttribSize._16_16, VertexAttribPointerType.Short }, + { GalVertexAttribSize._32, VertexAttribPointerType.Int }, + { GalVertexAttribSize._8_8_8, VertexAttribPointerType.Byte }, + { GalVertexAttribSize._8_8, VertexAttribPointerType.Byte }, + { GalVertexAttribSize._16, VertexAttribPointerType.Short }, + { GalVertexAttribSize._8, VertexAttribPointerType.Byte }, + { GalVertexAttribSize._10_10_10_2, VertexAttribPointerType.Int }, //? + { GalVertexAttribSize._11_11_10, VertexAttribPointerType.Int } //? + }; + + private struct VbInfo + { + public int VaoHandle; + public int VboHandle; + + public int PrimCount; + } + + private struct IbInfo + { + public int IboHandle; + public int Count; + + public DrawElementsType Type; + } + + private VbInfo[] VertexBuffers; + + private IbInfo IndexBuffer; + + public OGLRasterizer() + { + VertexBuffers = new VbInfo[32]; + + IndexBuffer = new IbInfo(); + } + + public void ClearBuffers(int RtIndex, GalClearBufferFlags Flags) + { + ClearBufferMask Mask = 0; + + //OpenGL doesn't support clearing just a single color channel, + //so we can't just clear all channels... + if (Flags.HasFlag(GalClearBufferFlags.ColorRed) && + Flags.HasFlag(GalClearBufferFlags.ColorGreen) && + Flags.HasFlag(GalClearBufferFlags.ColorBlue) && + Flags.HasFlag(GalClearBufferFlags.ColorAlpha)) + { + Mask = ClearBufferMask.ColorBufferBit; + } + + if (Flags.HasFlag(GalClearBufferFlags.Depth)) + { + Mask |= ClearBufferMask.DepthBufferBit; + } + + if (Flags.HasFlag(GalClearBufferFlags.Stencil)) + { + Mask |= ClearBufferMask.StencilBufferBit; + } + + GL.Clear(Mask); + } + + public void SetVertexArray(int VbIndex, int Stride, byte[] Buffer, GalVertexAttrib[] Attribs) + { + EnsureVbInitialized(VbIndex); + + VertexBuffers[VbIndex].PrimCount = Buffer.Length / Stride; + + VbInfo Vb = VertexBuffers[VbIndex]; + + IntPtr Length = new IntPtr(Buffer.Length); + + GL.BindBuffer(BufferTarget.ArrayBuffer, Vb.VboHandle); + GL.BufferData(BufferTarget.ArrayBuffer, Length, Buffer, BufferUsageHint.StreamDraw); + GL.BindBuffer(BufferTarget.ArrayBuffer, 0); + + GL.BindVertexArray(Vb.VaoHandle); + + for (int Attr = 0; Attr < 16; Attr++) + { + GL.DisableVertexAttribArray(Attr); + } + + for (int Index = 0; Index < Attribs.Length; Index++) + { + GalVertexAttrib Attrib = Attribs[Index]; + + GL.EnableVertexAttribArray(Index); + + GL.BindBuffer(BufferTarget.ArrayBuffer, Vb.VboHandle); + + bool Unsigned = + Attrib.Type == GalVertexAttribType.Unorm || + Attrib.Type == GalVertexAttribType.Uint || + Attrib.Type == GalVertexAttribType.Uscaled; + + bool Normalize = + Attrib.Type == GalVertexAttribType.Snorm || + Attrib.Type == GalVertexAttribType.Unorm; + + VertexAttribPointerType Type = 0; + + if (Attrib.Type == GalVertexAttribType.Float) + { + Type = VertexAttribPointerType.Float; + } + else + { + Type = AttribTypes[Attrib.Size] + (Unsigned ? 1 : 0); + } + + int Size = AttribElements[Attrib.Size]; + int Offset = Attrib.Offset; + + GL.VertexAttribPointer(Index, Size, Type, Normalize, Stride, Offset); + } + + GL.BindVertexArray(0); + } + + public void SetIndexArray(byte[] Buffer, GalIndexFormat Format) + { + EnsureIbInitialized(); + + IndexBuffer.Type = OGLEnumConverter.GetDrawElementsType(Format); + + IndexBuffer.Count = Buffer.Length >> (int)Format; + + IntPtr Length = new IntPtr(Buffer.Length); + + GL.BindBuffer(BufferTarget.ElementArrayBuffer, IndexBuffer.IboHandle); + GL.BufferData(BufferTarget.ElementArrayBuffer, Length, Buffer, BufferUsageHint.StreamDraw); + GL.BindBuffer(BufferTarget.ElementArrayBuffer, 0); + } + + public void DrawArrays(int VbIndex, GalPrimitiveType PrimType) + { + VbInfo Vb = VertexBuffers[VbIndex]; + + if (Vb.PrimCount == 0) + { + return; + } + + GL.BindVertexArray(Vb.VaoHandle); + + GL.DrawArrays(OGLEnumConverter.GetPrimitiveType(PrimType), 0, Vb.PrimCount); + } + + public void DrawElements(int VbIndex, int First, GalPrimitiveType PrimType) + { + VbInfo Vb = VertexBuffers[VbIndex]; + + if (Vb.PrimCount == 0) + { + return; + } + + PrimitiveType Mode = OGLEnumConverter.GetPrimitiveType(PrimType); + + GL.BindVertexArray(Vb.VaoHandle); + + GL.BindBuffer(BufferTarget.ElementArrayBuffer, IndexBuffer.IboHandle); + + GL.DrawElements(Mode, IndexBuffer.Count, IndexBuffer.Type, First); + } + + private void EnsureVbInitialized(int VbIndex) + { + VbInfo Vb = VertexBuffers[VbIndex]; + + if (Vb.VaoHandle == 0) + { + Vb.VaoHandle = GL.GenVertexArray(); + } + + if (Vb.VboHandle == 0) + { + Vb.VboHandle = GL.GenBuffer(); + } + + VertexBuffers[VbIndex] = Vb; + } + + private void EnsureIbInitialized() + { + if (IndexBuffer.IboHandle == 0) + { + IndexBuffer.IboHandle = GL.GenBuffer(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs new file mode 100644 index 000000000..fff6362b4 --- /dev/null +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs @@ -0,0 +1,262 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Graphics.Gal.Shader; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Ryujinx.Graphics.Gal.OpenGL +{ + class OGLShader + { + private class ShaderStage : IDisposable + { + public int Handle { get; private set; } + + public bool IsCompiled { get; private set; } + + public GalShaderType Type { get; private set; } + + public string Code { get; private set; } + + public IEnumerable TextureUsage { get; private set; } + public IEnumerable UniformUsage { get; private set; } + + public ShaderStage( + GalShaderType Type, + string Code, + IEnumerable TextureUsage, + IEnumerable UniformUsage) + { + this.Type = Type; + this.Code = Code; + this.TextureUsage = TextureUsage; + this.UniformUsage = UniformUsage; + } + + public void Compile() + { + if (Handle == 0) + { + Handle = GL.CreateShader(OGLEnumConverter.GetShaderType(Type)); + + CompileAndCheck(Handle, Code); + } + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool Disposing) + { + if (Disposing && Handle != 0) + { + GL.DeleteShader(Handle); + + Handle = 0; + } + } + } + + private struct ShaderProgram + { + public ShaderStage Vertex; + public ShaderStage TessControl; + public ShaderStage TessEvaluation; + public ShaderStage Geometry; + public ShaderStage Fragment; + } + + private ShaderProgram Current; + + private ConcurrentDictionary Stages; + + private Dictionary Programs; + + public int CurrentProgramHandle { get; private set; } + + public OGLShader() + { + Stages = new ConcurrentDictionary(); + + Programs = new Dictionary(); + } + + public void Create(long Tag, GalShaderType Type, byte[] Data) + { + Stages.GetOrAdd(Tag, (Key) => ShaderStageFactory(Type, Data)); + } + + private ShaderStage ShaderStageFactory(GalShaderType Type, byte[] Data) + { + GlslProgram Program = GetGlslProgram(Data, Type); + + return new ShaderStage( + Type, + Program.Code, + Program.Textures, + Program.Uniforms); + } + + private GlslProgram GetGlslProgram(byte[] Data, GalShaderType Type) + { + int[] Code = new int[(Data.Length - 0x50) >> 2]; + + using (MemoryStream MS = new MemoryStream(Data)) + { + MS.Seek(0x50, SeekOrigin.Begin); + + BinaryReader Reader = new BinaryReader(MS); + + for (int Index = 0; Index < Code.Length; Index++) + { + Code[Index] = Reader.ReadInt32(); + } + } + + GlslDecompiler Decompiler = new GlslDecompiler(); + + return Decompiler.Decompile(Code, Type); + } + + public IEnumerable GetTextureUsage(long Tag) + { + if (Stages.TryGetValue(Tag, out ShaderStage Stage)) + { + return Stage.TextureUsage; + } + + return Enumerable.Empty(); + } + + public void SetConstBuffer(long Tag, int Cbuf, byte[] Data) + { + BindProgram(); + + if (Stages.TryGetValue(Tag, out ShaderStage Stage)) + { + foreach (ShaderDeclInfo DeclInfo in Stage.UniformUsage.Where(x => x.Cbuf == Cbuf)) + { + float Value = BitConverter.ToSingle(Data, DeclInfo.Index * 4); + + int Location = GL.GetUniformLocation(CurrentProgramHandle, DeclInfo.Name); + + GL.Uniform1(Location, Value); + } + } + } + + public void SetUniform1(string UniformName, int Value) + { + BindProgram(); + + int Location = GL.GetUniformLocation(CurrentProgramHandle, UniformName); + + GL.Uniform1(Location, Value); + } + + public void SetUniform2F(string UniformName, float X, float Y) + { + BindProgram(); + + int Location = GL.GetUniformLocation(CurrentProgramHandle, UniformName); + + GL.Uniform2(Location, X, Y); + } + + public void Bind(long Tag) + { + if (Stages.TryGetValue(Tag, out ShaderStage Stage)) + { + Bind(Stage); + } + } + + private void Bind(ShaderStage Stage) + { + switch (Stage.Type) + { + case GalShaderType.Vertex: Current.Vertex = Stage; break; + case GalShaderType.TessControl: Current.TessControl = Stage; break; + case GalShaderType.TessEvaluation: Current.TessEvaluation = Stage; break; + case GalShaderType.Geometry: Current.Geometry = Stage; break; + case GalShaderType.Fragment: Current.Fragment = Stage; break; + } + } + + public void BindProgram() + { + if (Current.Vertex == null || + Current.Fragment == null) + { + return; + } + + if (!Programs.TryGetValue(Current, out int Handle)) + { + Handle = GL.CreateProgram(); + + AttachIfNotNull(Handle, Current.Vertex); + AttachIfNotNull(Handle, Current.TessControl); + AttachIfNotNull(Handle, Current.TessEvaluation); + AttachIfNotNull(Handle, Current.Geometry); + AttachIfNotNull(Handle, Current.Fragment); + + GL.LinkProgram(Handle); + + CheckProgramLink(Handle); + + Programs.Add(Current, Handle); + } + + GL.UseProgram(Handle); + + CurrentProgramHandle = Handle; + } + + private void AttachIfNotNull(int ProgramHandle, ShaderStage Stage) + { + if (Stage != null) + { + Stage.Compile(); + + GL.AttachShader(ProgramHandle, Stage.Handle); + } + } + + public static void CompileAndCheck(int Handle, string Code) + { + GL.ShaderSource(Handle, Code); + GL.CompileShader(Handle); + + CheckCompilation(Handle); + } + + private static void CheckCompilation(int Handle) + { + int Status = 0; + + GL.GetShader(Handle, ShaderParameter.CompileStatus, out Status); + + if (Status == 0) + { + throw new ShaderException(GL.GetShaderInfoLog(Handle)); + } + } + + private static void CheckProgramLink(int Handle) + { + int Status = 0; + + GL.GetProgram(Handle, GetProgramParameterName.LinkStatus, out Status); + + if (Status == 0) + { + throw new ShaderException(GL.GetProgramInfoLog(Handle)); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLTexture.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLTexture.cs new file mode 100644 index 000000000..9ea25056c --- /dev/null +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLTexture.cs @@ -0,0 +1,115 @@ +using OpenTK.Graphics.OpenGL; + +namespace Ryujinx.Graphics.Gal.OpenGL +{ + class OGLTexture + { + private int[] Textures; + + public OGLTexture() + { + Textures = new int[80]; + } + + public void Set(int Index, GalTexture Texture) + { + GL.ActiveTexture(TextureUnit.Texture0 + Index); + + Bind(Index); + + const int Level = 0; //TODO: Support mipmap textures. + const int Border = 0; + + if (IsCompressedTextureFormat(Texture.Format)) + { + PixelInternalFormat InternalFmt = OGLEnumConverter.GetCompressedTextureFormat(Texture.Format); + + GL.CompressedTexImage2D( + TextureTarget.Texture2D, + Level, + InternalFmt, + Texture.Width, + Texture.Height, + Border, + Texture.Data.Length, + Texture.Data); + } + else + { + const PixelInternalFormat InternalFmt = PixelInternalFormat.Rgba; + + (PixelFormat Format, PixelType Type) = OGLEnumConverter.GetTextureFormat(Texture.Format); + + GL.TexImage2D( + TextureTarget.Texture2D, + Level, + InternalFmt, + Texture.Width, + Texture.Height, + Border, + Format, + Type, + Texture.Data); + } + } + + public void Bind(int Index) + { + int Handle = EnsureTextureInitialized(Index); + + GL.BindTexture(TextureTarget.Texture2D, Handle); + } + + public static void Set(GalTextureSampler Sampler) + { + int WrapS = (int)OGLEnumConverter.GetTextureWrapMode(Sampler.AddressU); + int WrapT = (int)OGLEnumConverter.GetTextureWrapMode(Sampler.AddressV); + + int MinFilter = (int)OGLEnumConverter.GetTextureMinFilter(Sampler.MinFilter, Sampler.MipFilter); + int MagFilter = (int)OGLEnumConverter.GetTextureMagFilter(Sampler.MagFilter); + + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, WrapS); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, WrapT); + + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, MinFilter); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, MagFilter); + + float[] Color = new float[] + { + Sampler.BorderColor.Red, + Sampler.BorderColor.Green, + Sampler.BorderColor.Blue, + Sampler.BorderColor.Alpha + }; + + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureBorderColor, Color); + } + + private static bool IsCompressedTextureFormat(GalTextureFormat Format) + { + switch (Format) + { + case GalTextureFormat.BC1: + case GalTextureFormat.BC2: + case GalTextureFormat.BC3: + case GalTextureFormat.BC4: + case GalTextureFormat.BC5: + return true; + } + + return false; + } + + private int EnsureTextureInitialized(int TexIndex) + { + int Handle = Textures[TexIndex]; + + if (Handle == 0) + { + Handle = Textures[TexIndex] = GL.GenTexture(); + } + + return Handle; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OpenGLRenderer.cs b/Ryujinx.Graphics/Gal/OpenGL/OpenGLRenderer.cs index 002e54dad..cf2da91c5 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OpenGLRenderer.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OpenGLRenderer.cs @@ -1,5 +1,4 @@ using OpenTK; -using OpenTK.Graphics.OpenGL; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -8,46 +7,33 @@ namespace Ryujinx.Graphics.Gal.OpenGL { public class OpenGLRenderer : IGalRenderer { - private struct VertexBuffer - { - public int VaoHandle; - public int VboHandle; + private OGLBlend Blend; - public int PrimCount; - } + private OGLFrameBuffer FrameBuffer; - private struct Texture - { - public int Handle; - } + private OGLRasterizer Rasterizer; - private List VertexBuffers; + private OGLShader Shader; - private Texture[] Textures; + private OGLTexture Texture; private ConcurrentQueue ActionsQueue; - private FrameBuffer FbRenderer; - public OpenGLRenderer() { - VertexBuffers = new List(); + Blend = new OGLBlend(); - Textures = new Texture[8]; + FrameBuffer = new OGLFrameBuffer(); + + Rasterizer = new OGLRasterizer(); + + Shader = new OGLShader(); + + Texture = new OGLTexture(); ActionsQueue = new ConcurrentQueue(); } - public void InitializeFrameBuffer() - { - FbRenderer = new FrameBuffer(1280, 720); - } - - public void ResetFrameBuffer() - { - FbRenderer.Reset(); - } - public void QueueAction(Action ActionMthd) { ActionsQueue.Enqueue(ActionMthd); @@ -65,259 +51,216 @@ namespace Ryujinx.Graphics.Gal.OpenGL public void Render() { - FbRenderer.Render(); - - for (int Index = 0; Index < VertexBuffers.Count; Index++) - { - VertexBuffer Vb = VertexBuffers[Index]; - - if (Vb.VaoHandle != 0 && - Vb.PrimCount != 0) - { - GL.BindVertexArray(Vb.VaoHandle); - GL.DrawArrays(PrimitiveType.TriangleStrip, 0, Vb.PrimCount); - } - } + FrameBuffer.Render(); } public void SetWindowSize(int Width, int Height) { - FbRenderer.WindowWidth = Width; - FbRenderer.WindowHeight = Height; + FrameBuffer.SetWindowSize(Width, Height); } - public unsafe void SetFrameBuffer( - byte* Fb, - int Width, - int Height, - float ScaleX, - float ScaleY, - float OffsX, - float OffsY, - float Rotate) + public void SetBlendEnable(bool Enable) + { + if (Enable) + { + ActionsQueue.Enqueue(() => Blend.Enable()); + } + else + { + ActionsQueue.Enqueue(() => Blend.Disable()); + } + } + + public void SetBlend( + GalBlendEquation Equation, + GalBlendFactor FuncSrc, + GalBlendFactor FuncDst) + { + ActionsQueue.Enqueue(() => Blend.Set(Equation, FuncSrc, FuncDst)); + } + + public void SetBlendSeparate( + GalBlendEquation EquationRgb, + GalBlendEquation EquationAlpha, + GalBlendFactor FuncSrcRgb, + GalBlendFactor FuncDstRgb, + GalBlendFactor FuncSrcAlpha, + GalBlendFactor FuncDstAlpha) + { + ActionsQueue.Enqueue(() => + { + Blend.SetSeparate( + EquationRgb, + EquationAlpha, + FuncSrcRgb, + FuncDstRgb, + FuncSrcAlpha, + FuncDstAlpha); + }); + } + + public void CreateFrameBuffer(long Tag, int Width, int Height) + { + ActionsQueue.Enqueue(() => FrameBuffer.Create(Tag, Width, Height)); + } + + public void BindFrameBuffer(long Tag) + { + ActionsQueue.Enqueue(() => FrameBuffer.Bind(Tag)); + } + + public void BindFrameBufferTexture(long Tag, int Index, GalTextureSampler Sampler) + { + ActionsQueue.Enqueue(() => + { + FrameBuffer.BindTexture(Tag, Index); + + OGLTexture.Set(Sampler); + }); + } + + public void SetFrameBuffer(long Tag) + { + ActionsQueue.Enqueue(() => FrameBuffer.Set(Tag)); + } + + public void SetFrameBuffer(byte[] Data, int Width, int Height) + { + ActionsQueue.Enqueue(() => FrameBuffer.Set(Data, Width, Height)); + } + + public void SetFrameBufferTransform(float SX, float SY, float Rotate, float TX, float TY) { Matrix2 Transform; - Transform = Matrix2.CreateScale(ScaleX, ScaleY); + Transform = Matrix2.CreateScale(SX, SY); Transform *= Matrix2.CreateRotation(Rotate); - Vector2 Offs = new Vector2(OffsX, OffsY); + Vector2 Offs = new Vector2(TX, TY); - FbRenderer.Set(Fb, Width, Height, Transform, Offs); + ActionsQueue.Enqueue(() => FrameBuffer.SetTransform(Transform, Offs)); } - public void SendVertexBuffer(int Index, byte[] Buffer, int Stride, GalVertexAttrib[] Attribs) + public void SetViewport(int X, int Y, int Width, int Height) { - if (Index < 0) - { - throw new ArgumentOutOfRangeException(nameof(Index)); - } - - if (Buffer.Length == 0 || Stride == 0) - { - return; - } - - EnsureVbInitialized(Index); - - VertexBuffer Vb = VertexBuffers[Index]; - - Vb.PrimCount = Buffer.Length / Stride; - - VertexBuffers[Index] = Vb; - - IntPtr Length = new IntPtr(Buffer.Length); - - GL.BindBuffer(BufferTarget.ArrayBuffer, Vb.VboHandle); - GL.BufferData(BufferTarget.ArrayBuffer, Length, Buffer, BufferUsageHint.StreamDraw); - GL.BindBuffer(BufferTarget.ArrayBuffer, 0); - - GL.BindVertexArray(Vb.VaoHandle); - - for (int Attr = 0; Attr < 16; Attr++) - { - GL.DisableVertexAttribArray(Attr); - } - - foreach (GalVertexAttrib Attrib in Attribs) - { - if (Attrib.Index >= 3) break; - - GL.EnableVertexAttribArray(Attrib.Index); - - GL.BindBuffer(BufferTarget.ArrayBuffer, Vb.VboHandle); - - int Size = 0; - - switch (Attrib.Size) - { - case GalVertexAttribSize._8: - case GalVertexAttribSize._16: - case GalVertexAttribSize._32: - Size = 1; - break; - case GalVertexAttribSize._8_8: - case GalVertexAttribSize._16_16: - case GalVertexAttribSize._32_32: - Size = 2; - break; - case GalVertexAttribSize._8_8_8: - case GalVertexAttribSize._11_11_10: - case GalVertexAttribSize._16_16_16: - case GalVertexAttribSize._32_32_32: - Size = 3; - break; - case GalVertexAttribSize._8_8_8_8: - case GalVertexAttribSize._10_10_10_2: - case GalVertexAttribSize._16_16_16_16: - case GalVertexAttribSize._32_32_32_32: - Size = 4; - break; - } - - bool Signed = - Attrib.Type == GalVertexAttribType.Snorm || - Attrib.Type == GalVertexAttribType.Sint || - Attrib.Type == GalVertexAttribType.Sscaled; - - bool Normalize = - Attrib.Type == GalVertexAttribType.Snorm || - Attrib.Type == GalVertexAttribType.Unorm; - - VertexAttribPointerType Type = 0; - - switch (Attrib.Type) - { - case GalVertexAttribType.Snorm: - case GalVertexAttribType.Unorm: - case GalVertexAttribType.Sint: - case GalVertexAttribType.Uint: - case GalVertexAttribType.Uscaled: - case GalVertexAttribType.Sscaled: - { - switch (Attrib.Size) - { - case GalVertexAttribSize._8: - case GalVertexAttribSize._8_8: - case GalVertexAttribSize._8_8_8: - case GalVertexAttribSize._8_8_8_8: - { - Type = Signed - ? VertexAttribPointerType.Byte - : VertexAttribPointerType.UnsignedByte; - - break; - } - - case GalVertexAttribSize._16: - case GalVertexAttribSize._16_16: - case GalVertexAttribSize._16_16_16: - case GalVertexAttribSize._16_16_16_16: - { - Type = Signed - ? VertexAttribPointerType.Short - : VertexAttribPointerType.UnsignedShort; - - break; - } - - case GalVertexAttribSize._10_10_10_2: - case GalVertexAttribSize._11_11_10: - case GalVertexAttribSize._32: - case GalVertexAttribSize._32_32: - case GalVertexAttribSize._32_32_32: - case GalVertexAttribSize._32_32_32_32: - { - Type = Signed - ? VertexAttribPointerType.Int - : VertexAttribPointerType.UnsignedInt; - - break; - } - } - - break; - } - - case GalVertexAttribType.Float: - { - Type = VertexAttribPointerType.Float; - - break; - } - } - - GL.VertexAttribPointer( - Attrib.Index, - Size, - Type, - Normalize, - Stride, - Attrib.Offset); - } - - GL.BindVertexArray(0); + ActionsQueue.Enqueue(() => FrameBuffer.SetViewport(X, Y, Width, Height)); } - public void SendR8G8B8A8Texture(int Index, byte[] Buffer, int Width, int Height) + public void ClearBuffers(int RtIndex, GalClearBufferFlags Flags) { - EnsureTexInitialized(Index); + ActionsQueue.Enqueue(() => Rasterizer.ClearBuffers(RtIndex, Flags)); + } - GL.BindTexture(TextureTarget.Texture2D, Textures[Index].Handle); - GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat); - GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat); - GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); - GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); - GL.TexImage2D(TextureTarget.Texture2D, - 0, - PixelInternalFormat.Rgba, - Width, - Height, - 0, - PixelFormat.Rgba, - PixelType.UnsignedByte, - Buffer); + public void SetVertexArray(int VbIndex, int Stride, byte[] Buffer, GalVertexAttrib[] Attribs) + { + if ((uint)VbIndex > 31) + { + throw new ArgumentOutOfRangeException(nameof(VbIndex)); + } + + ActionsQueue.Enqueue(() => Rasterizer.SetVertexArray(VbIndex, Stride, + Buffer ?? throw new ArgumentNullException(nameof(Buffer)), + Attribs ?? throw new ArgumentNullException(nameof(Attribs)))); + } + + public void SetIndexArray(byte[] Buffer, GalIndexFormat Format) + { + if (Buffer == null) + { + throw new ArgumentNullException(nameof(Buffer)); + } + + ActionsQueue.Enqueue(() => Rasterizer.SetIndexArray(Buffer, Format)); + } + + public void DrawArrays(int VbIndex, GalPrimitiveType PrimType) + { + if ((uint)VbIndex > 31) + { + throw new ArgumentOutOfRangeException(nameof(VbIndex)); + } + + ActionsQueue.Enqueue(() => Rasterizer.DrawArrays(VbIndex, PrimType)); + } + + public void DrawElements(int VbIndex, int First, GalPrimitiveType PrimType) + { + if ((uint)VbIndex > 31) + { + throw new ArgumentOutOfRangeException(nameof(VbIndex)); + } + + ActionsQueue.Enqueue(() => Rasterizer.DrawElements(VbIndex, First, PrimType)); + } + + public void CreateShader(long Tag, GalShaderType Type, byte[] Data) + { + if (Data == null) + { + throw new ArgumentNullException(nameof(Data)); + } + + Shader.Create(Tag, Type, Data); + } + + public void SetConstBuffer(long Tag, int Cbuf, byte[] Data) + { + if (Data == null) + { + throw new ArgumentNullException(nameof(Data)); + } + + ActionsQueue.Enqueue(() => Shader.SetConstBuffer(Tag, Cbuf, Data)); + } + + public void SetUniform1(string UniformName, int Value) + { + if (UniformName == null) + { + throw new ArgumentNullException(nameof(UniformName)); + } + + ActionsQueue.Enqueue(() => Shader.SetUniform1(UniformName, Value)); + } + + public void SetUniform2F(string UniformName, float X, float Y) + { + if (UniformName == null) + { + throw new ArgumentNullException(nameof(UniformName)); + } + + ActionsQueue.Enqueue(() => Shader.SetUniform2F(UniformName, X, Y)); + } + + public IEnumerable GetTextureUsage(long Tag) + { + return Shader.GetTextureUsage(Tag); + } + + public void BindShader(long Tag) + { + ActionsQueue.Enqueue(() => Shader.Bind(Tag)); + } + + public void BindProgram() + { + ActionsQueue.Enqueue(() => Shader.BindProgram()); + } + + public void SetTextureAndSampler(int Index, GalTexture Texture, GalTextureSampler Sampler) + { + ActionsQueue.Enqueue(() => + { + this.Texture.Set(Index, Texture); + + OGLTexture.Set(Sampler); + }); } public void BindTexture(int Index) { - GL.ActiveTexture(TextureUnit.Texture0 + Index); - - GL.BindTexture(TextureTarget.Texture2D, Textures[Index].Handle); - } - - private void EnsureVbInitialized(int VbIndex) - { - while (VbIndex >= VertexBuffers.Count) - { - VertexBuffers.Add(new VertexBuffer()); - } - - VertexBuffer Vb = VertexBuffers[VbIndex]; - - if (Vb.VaoHandle == 0) - { - Vb.VaoHandle = GL.GenVertexArray(); - } - - if (Vb.VboHandle == 0) - { - Vb.VboHandle = GL.GenBuffer(); - } - - VertexBuffers[VbIndex] = Vb; - } - - private void EnsureTexInitialized(int TexIndex) - { - Texture Tex = Textures[TexIndex]; - - if (Tex.Handle == 0) - { - Tex.Handle = GL.GenTexture(); - } - - Textures[TexIndex] = Tex; + ActionsQueue.Enqueue(() => Texture.Bind(Index)); } } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs b/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs new file mode 100644 index 000000000..cd901747d --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs @@ -0,0 +1,214 @@ +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Gal.Shader +{ + class GlslDecl + { + public const int VertexIdAttr = 0x2fc; + public const int GlPositionWAttr = 0x7c; + + private const int AttrStartIndex = 8; + private const int TexStartIndex = 8; + + public const string PositionOutAttrName = "position"; + + private const string InAttrName = "in_attr"; + private const string OutAttrName = "out_attr"; + private const string UniformName = "c"; + + private const string GprName = "gpr"; + private const string PredName = "pred"; + private const string TextureName = "tex"; + + public const string FragmentOutputName = "FragColor"; + + private string[] StagePrefixes = new string[] { "vp", "tcp", "tep", "gp", "fp" }; + + private string StagePrefix; + + private Dictionary m_Textures; + + private Dictionary<(int, int), ShaderDeclInfo> m_Uniforms; + + private Dictionary m_InAttributes; + private Dictionary m_OutAttributes; + + private Dictionary m_Gprs; + private Dictionary m_Preds; + + public IReadOnlyDictionary Textures => m_Textures; + + public IReadOnlyDictionary<(int, int), ShaderDeclInfo> Uniforms => m_Uniforms; + + public IReadOnlyDictionary InAttributes => m_InAttributes; + public IReadOnlyDictionary OutAttributes => m_OutAttributes; + + public IReadOnlyDictionary Gprs => m_Gprs; + public IReadOnlyDictionary Preds => m_Preds; + + public GalShaderType ShaderType { get; private set; } + + public GlslDecl(ShaderIrNode[] Nodes, GalShaderType ShaderType) + { + this.ShaderType = ShaderType; + + StagePrefix = StagePrefixes[(int)ShaderType] + "_"; + + m_Uniforms = new Dictionary<(int, int), ShaderDeclInfo>(); + + m_Textures = new Dictionary(); + + m_InAttributes = new Dictionary(); + m_OutAttributes = new Dictionary(); + + m_Gprs = new Dictionary(); + m_Preds = new Dictionary(); + + if (ShaderType == GalShaderType.Fragment) + { + m_Gprs.Add(0, new ShaderDeclInfo(FragmentOutputName, 0, 0, 4)); + + m_InAttributes.Add(7, new ShaderDeclInfo(PositionOutAttrName, -1, 0, 4)); + } + else + { + m_OutAttributes.Add(7, new ShaderDeclInfo("gl_Position", -1, 0, 4)); + } + + foreach (ShaderIrNode Node in Nodes) + { + Traverse(null, Node); + } + } + + private void Traverse(ShaderIrNode Parent, ShaderIrNode Node) + { + switch (Node) + { + case ShaderIrAsg Asg: + { + Traverse(Asg, Asg.Dst); + Traverse(Asg, Asg.Src); + + break; + } + + case ShaderIrCond Cond: + { + Traverse(Cond, Cond.Pred); + Traverse(Cond, Cond.Child); + + break; + } + + case ShaderIrOp Op: + { + Traverse(Op, Op.OperandA); + Traverse(Op, Op.OperandB); + Traverse(Op, Op.OperandC); + + if (Op.Inst == ShaderIrInst.Texq || + Op.Inst == ShaderIrInst.Texs || + Op.Inst == ShaderIrInst.Txlf) + { + int Handle = ((ShaderIrOperImm)Op.OperandC).Value; + + int Index = Handle - TexStartIndex; + + string Name = StagePrefix + TextureName + Index; + + m_Textures.TryAdd(Handle, new ShaderDeclInfo(Name, Handle)); + } + break; + } + + case ShaderIrOperCbuf Cbuf: + { + string Name = StagePrefix + UniformName + Cbuf.Index + "_" + Cbuf.Offs; + + ShaderDeclInfo DeclInfo = new ShaderDeclInfo(Name, Cbuf.Offs, Cbuf.Index); + + m_Uniforms.TryAdd((Cbuf.Index, Cbuf.Offs), DeclInfo); + + break; + } + + case ShaderIrOperAbuf Abuf: + { + //This is a built-in input variable. + if (Abuf.Offs == VertexIdAttr) + { + break; + } + + int Index = Abuf.Offs >> 4; + int Elem = (Abuf.Offs >> 2) & 3; + + int GlslIndex = Index - AttrStartIndex; + + ShaderDeclInfo DeclInfo; + + if (Parent is ShaderIrAsg Asg && Asg.Dst == Node) + { + if (!m_OutAttributes.TryGetValue(Index, out DeclInfo)) + { + DeclInfo = new ShaderDeclInfo(OutAttrName + GlslIndex, GlslIndex); + + m_OutAttributes.Add(Index, DeclInfo); + } + } + else + { + if (!m_InAttributes.TryGetValue(Index, out DeclInfo)) + { + DeclInfo = new ShaderDeclInfo(InAttrName + GlslIndex, GlslIndex); + + m_InAttributes.Add(Index, DeclInfo); + } + } + + DeclInfo.Enlarge(Elem + 1); + + break; + } + + case ShaderIrOperGpr Gpr: + { + if (!Gpr.IsConst && !HasName(m_Gprs, Gpr.Index)) + { + string Name = GprName + Gpr.Index; + + m_Gprs.TryAdd(Gpr.Index, new ShaderDeclInfo(Name, Gpr.Index)); + } + break; + } + + case ShaderIrOperPred Pred: + { + if (!Pred.IsConst && !HasName(m_Preds, Pred.Index)) + { + string Name = PredName + Pred.Index; + + m_Preds.TryAdd(Pred.Index, new ShaderDeclInfo(Name, Pred.Index)); + } + break; + } + } + } + + private bool HasName(Dictionary Decls, int Index) + { + int VecIndex = Index >> 2; + + if (Decls.TryGetValue(VecIndex, out ShaderDeclInfo DeclInfo)) + { + if (DeclInfo.Size > 1 && Index < VecIndex + DeclInfo.Size) + { + return true; + } + } + + return Decls.ContainsKey(Index); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs b/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs new file mode 100644 index 000000000..d7173bcd6 --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs @@ -0,0 +1,776 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; + +namespace Ryujinx.Graphics.Gal.Shader +{ + class GlslDecompiler + { + private delegate string GetInstExpr(ShaderIrOp Op); + + private Dictionary InstsExpr; + + private enum OperType + { + Bool, + F32, + I32 + } + + private const string IdentationStr = " "; + + private static string[] ElemTypes = new string[] { "float", "vec2", "vec3", "vec4" }; + + private GlslDecl Decl; + + private StringBuilder SB; + + public GlslDecompiler() + { + InstsExpr = new Dictionary() + { + { ShaderIrInst.And, GetAndExpr }, + { ShaderIrInst.Asr, GetAsrExpr }, + { ShaderIrInst.Band, GetBandExpr }, + { ShaderIrInst.Bnot, GetBnotExpr }, + { ShaderIrInst.Ceil, GetCeilExpr }, + { ShaderIrInst.Ceq, GetCeqExpr }, + { ShaderIrInst.Cge, GetCgeExpr }, + { ShaderIrInst.Cgt, GetCgtExpr }, + { ShaderIrInst.Clamp, GetClampExpr }, + { ShaderIrInst.Cle, GetCleExpr }, + { ShaderIrInst.Clt, GetCltExpr }, + { ShaderIrInst.Cne, GetCneExpr }, + { ShaderIrInst.Exit, GetExitExpr }, + { ShaderIrInst.Fabs, GetFabsExpr }, + { ShaderIrInst.Fadd, GetFaddExpr }, + { ShaderIrInst.Fceq, GetCeqExpr }, + { ShaderIrInst.Fcge, GetCgeExpr }, + { ShaderIrInst.Fcgt, GetCgtExpr }, + { ShaderIrInst.Fcle, GetCleExpr }, + { ShaderIrInst.Fclt, GetCltExpr }, + { ShaderIrInst.Fcne, GetCneExpr }, + { ShaderIrInst.Fcos, GetFcosExpr }, + { ShaderIrInst.Fex2, GetFex2Expr }, + { ShaderIrInst.Ffma, GetFfmaExpr }, + { ShaderIrInst.Flg2, GetFlg2Expr }, + { ShaderIrInst.Floor, GetFloorExpr }, + { ShaderIrInst.Fmul, GetFmulExpr }, + { ShaderIrInst.Fneg, GetFnegExpr }, + { ShaderIrInst.Frcp, GetFrcpExpr }, + { ShaderIrInst.Frsq, GetFrsqExpr }, + { ShaderIrInst.Fsin, GetFsinExpr }, + { ShaderIrInst.Ftos, GetFtosExpr }, + { ShaderIrInst.Ftou, GetFtouExpr }, + { ShaderIrInst.Ipa, GetIpaExpr }, + { ShaderIrInst.Kil, GetKilExpr }, + { ShaderIrInst.Lsr, GetLsrExpr }, + { ShaderIrInst.Not, GetNotExpr }, + { ShaderIrInst.Or, GetOrExpr }, + { ShaderIrInst.Stof, GetStofExpr }, + { ShaderIrInst.Texq, GetTexqExpr }, + { ShaderIrInst.Texs, GetTexsExpr }, + { ShaderIrInst.Trunc, GetTruncExpr }, + { ShaderIrInst.Txlf, GetTxlfExpr }, + { ShaderIrInst.Utof, GetUtofExpr }, + { ShaderIrInst.Xor, GetXorExpr } + }; + } + + public GlslProgram Decompile(int[] Code, GalShaderType ShaderType) + { + ShaderIrBlock Block = ShaderDecoder.DecodeBasicBlock(Code, 0); + + ShaderIrNode[] Nodes = Block.GetNodes(); + + Decl = new GlslDecl(Nodes, ShaderType); + + SB = new StringBuilder(); + + SB.AppendLine("#version 410 core"); + + PrintDeclTextures(); + PrintDeclUniforms(); + PrintDeclInAttributes(); + PrintDeclOutAttributes(); + PrintDeclGprs(); + PrintDeclPreds(); + + PrintBlockScope("void main()", 1, Nodes); + + string GlslCode = SB.ToString(); + + return new GlslProgram( + GlslCode, + Decl.Textures.Values, + Decl.Uniforms.Values); + } + + private void PrintDeclTextures() + { + PrintDecls(Decl.Textures, "uniform sampler2D"); + } + + private void PrintDeclUniforms() + { + if (Decl.ShaderType == GalShaderType.Vertex) + { + SB.AppendLine("uniform vec2 " + GalConsts.FlipUniformName + ";"); + } + + foreach (ShaderDeclInfo DeclInfo in Decl.Uniforms.Values.OrderBy(DeclKeySelector)) + { + SB.AppendLine($"uniform {GetDecl(DeclInfo)};"); + } + + if (Decl.Uniforms.Count > 0) + { + SB.AppendLine(); + } + } + + private void PrintDeclInAttributes() + { + if (Decl.ShaderType == GalShaderType.Fragment) + { + SB.AppendLine("in vec4 " + GlslDecl.PositionOutAttrName + ";"); + } + + PrintDeclAttributes(Decl.InAttributes.Values, "in"); + } + + private void PrintDeclOutAttributes() + { + if (Decl.ShaderType == GalShaderType.Vertex) + { + SB.AppendLine("out vec4 " + GlslDecl.PositionOutAttrName + ";"); + } + + PrintDeclAttributes(Decl.OutAttributes.Values, "out"); + } + + private void PrintDeclAttributes(IEnumerable Decls, string InOut) + { + int Count = 0; + + foreach (ShaderDeclInfo DeclInfo in Decls.OrderBy(DeclKeySelector)) + { + if (DeclInfo.Index >= 0) + { + SB.AppendLine("layout (location = " + DeclInfo.Index + ") " + InOut + " " + GetDecl(DeclInfo) + ";"); + + Count++; + } + } + + if (Count > 0) + { + SB.AppendLine(); + } + } + + private void PrintDeclGprs() + { + PrintDecls(Decl.Gprs); + } + + private void PrintDeclPreds() + { + PrintDecls(Decl.Preds, "bool"); + } + + private void PrintDecls(IReadOnlyDictionary Dict, string CustomType = null) + { + foreach (ShaderDeclInfo DeclInfo in Dict.Values.OrderBy(DeclKeySelector)) + { + string Name; + + if (CustomType != null) + { + Name = CustomType + " " + DeclInfo.Name + ";"; + } + else if (DeclInfo.Name == GlslDecl.FragmentOutputName) + { + Name = "layout (location = 0) out " + GetDecl(DeclInfo) + ";" + Environment.NewLine; + } + else + { + Name = GetDecl(DeclInfo) + ";"; + } + + SB.AppendLine(Name); + } + + if (Dict.Count > 0) + { + SB.AppendLine(); + } + } + + private int DeclKeySelector(ShaderDeclInfo DeclInfo) + { + return DeclInfo.Cbuf << 24 | DeclInfo.Index; + } + + private string GetDecl(ShaderDeclInfo DeclInfo) + { + return ElemTypes[DeclInfo.Size - 1] + " " + DeclInfo.Name; + } + + private void PrintBlockScope(string ScopeName, int IdentationLevel, params ShaderIrNode[] Nodes) + { + string Identation = string.Empty; + + for (int Index = 0; Index < IdentationLevel - 1; Index++) + { + Identation += IdentationStr; + } + + if (ScopeName != string.Empty) + { + ScopeName += " "; + } + + SB.AppendLine(Identation + ScopeName + "{"); + + string LastLine = Identation + "}"; + + if (IdentationLevel > 0) + { + Identation += IdentationStr; + } + + for (int Index = 0; Index < Nodes.Length; Index++) + { + ShaderIrNode Node = Nodes[Index]; + + if (Node is ShaderIrCond Cond) + { + string IfExpr = GetSrcExpr(Cond.Pred, true); + + if (Cond.Not) + { + IfExpr = "!(" + IfExpr + ")"; + } + + string SubScopeName = "if (" + IfExpr + ")"; + + PrintBlockScope(SubScopeName, IdentationLevel + 1, Cond.Child); + } + else if (Node is ShaderIrAsg Asg && IsValidOutOper(Asg.Dst)) + { + string Expr = GetSrcExpr(Asg.Src, true); + + Expr = GetExprWithCast(Asg.Dst, Asg.Src, Expr); + + SB.AppendLine(Identation + GetDstOperName(Asg.Dst) + " = " + Expr + ";"); + } + else if (Node is ShaderIrOp Op) + { + if (Op.Inst == ShaderIrInst.Exit) + { + //Do everything that needs to be done before + //the shader ends here. + if (Decl.ShaderType == GalShaderType.Vertex) + { + SB.AppendLine(Identation + "gl_Position.xy *= flip;"); + + SB.AppendLine(Identation + GlslDecl.PositionOutAttrName + " = gl_Position;"); + } + } + + SB.AppendLine(Identation + GetSrcExpr(Op, true) + ";"); + } + else + { + throw new InvalidOperationException(); + } + } + + SB.AppendLine(LastLine); + } + + private bool IsValidOutOper(ShaderIrNode Node) + { + if (Node is ShaderIrOperGpr Gpr && Gpr.IsConst) + { + return false; + } + else if (Node is ShaderIrOperPred Pred && Pred.IsConst) + { + return false; + } + + return true; + } + + private string GetDstOperName(ShaderIrNode Node) + { + if (Node is ShaderIrOperAbuf Abuf) + { + return GetOutAbufName(Abuf); + } + else if (Node is ShaderIrOperGpr Gpr) + { + return GetName(Gpr); + } + else if (Node is ShaderIrOperPred Pred) + { + return GetName(Pred); + } + + throw new ArgumentException(nameof(Node)); + } + + private string GetSrcExpr(ShaderIrNode Node, bool Entry = false) + { + switch (Node) + { + case ShaderIrOperAbuf Abuf: return GetName (Abuf); + case ShaderIrOperCbuf Cbuf: return GetName (Cbuf); + case ShaderIrOperGpr Gpr: return GetName (Gpr); + case ShaderIrOperImm Imm: return GetValue(Imm); + case ShaderIrOperImmf Immf: return GetValue(Immf); + case ShaderIrOperPred Pred: return GetName (Pred); + + case ShaderIrOp Op: + string Expr; + + if (InstsExpr.TryGetValue(Op.Inst, out GetInstExpr GetExpr)) + { + Expr = GetExpr(Op); + } + else + { + throw new NotImplementedException(Op.Inst.ToString()); + } + + if (!Entry && NeedsParentheses(Op)) + { + Expr = "(" + Expr + ")"; + } + + return Expr; + + default: throw new ArgumentException(nameof(Node)); + } + } + + private static bool NeedsParentheses(ShaderIrOp Op) + { + switch (Op.Inst) + { + case ShaderIrInst.Frcp: + return true; + + case ShaderIrInst.Ipa: + case ShaderIrInst.Texq: + case ShaderIrInst.Texs: + case ShaderIrInst.Txlf: + return false; + } + + return Op.OperandB != null || + Op.OperandC != null; + } + + private string GetName(ShaderIrOperCbuf Cbuf) + { + if (!Decl.Uniforms.TryGetValue((Cbuf.Index, Cbuf.Offs), out ShaderDeclInfo DeclInfo)) + { + throw new InvalidOperationException(); + } + + return DeclInfo.Name; + } + + private string GetOutAbufName(ShaderIrOperAbuf Abuf) + { + return GetName(Decl.OutAttributes, Abuf); + } + + private string GetName(ShaderIrOperAbuf Abuf) + { + if (Abuf.Offs == GlslDecl.VertexIdAttr) + { + return "gl_VertexID"; + } + + return GetName(Decl.InAttributes, Abuf); + } + + private string GetName(IReadOnlyDictionary Dict, ShaderIrOperAbuf Abuf) + { + int Index = Abuf.Offs >> 4; + int Elem = (Abuf.Offs >> 2) & 3; + + if (!Dict.TryGetValue(Index, out ShaderDeclInfo DeclInfo)) + { + throw new InvalidOperationException(); + } + + return DeclInfo.Size > 1 ? DeclInfo.Name + "." + GetAttrSwizzle(Elem) : DeclInfo.Name; + } + + private string GetName(ShaderIrOperGpr Gpr) + { + return Gpr.IsConst ? "0" : GetNameWithSwizzle(Decl.Gprs, Gpr.Index); + } + + private string GetValue(ShaderIrOperImm Imm) + { + //Only use hex is the value is too big and would likely be hard to read as int. + if (Imm.Value > 0xfff || + Imm.Value < -0xfff) + { + return "0x" + Imm.Value.ToString("x8", CultureInfo.InvariantCulture); + } + else + { + return Imm.Value.ToString(CultureInfo.InvariantCulture); + } + } + + private string GetValue(ShaderIrOperImmf Immf) + { + return Immf.Value.ToString(CultureInfo.InvariantCulture); + } + + private string GetName(ShaderIrOperPred Pred) + { + return Pred.IsConst ? "true" : GetNameWithSwizzle(Decl.Preds, Pred.Index); + } + + private string GetNameWithSwizzle(IReadOnlyDictionary Dict, int Index) + { + int VecIndex = Index >> 2; + + if (Dict.TryGetValue(VecIndex, out ShaderDeclInfo DeclInfo)) + { + if (DeclInfo.Size > 1 && Index < VecIndex + DeclInfo.Size) + { + return DeclInfo.Name + "." + GetAttrSwizzle(Index & 3); + } + } + + if (!Dict.TryGetValue(Index, out DeclInfo)) + { + throw new InvalidOperationException(); + } + + return DeclInfo.Name; + } + + private string GetAttrSwizzle(int Elem) + { + return "xyzw".Substring(Elem, 1); + } + + private string GetAndExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "&"); + + private string GetAsrExpr(ShaderIrOp Op) => GetBinaryExpr(Op, ">>"); + + private string GetBandExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "&&"); + + private string GetBnotExpr(ShaderIrOp Op) => GetUnaryExpr(Op, "!"); + + private string GetCeilExpr(ShaderIrOp Op) => GetUnaryCall(Op, "ceil"); + + private string GetClampExpr(ShaderIrOp Op) => GetTernaryCall(Op, "clamp"); + + private string GetCltExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "<"); + private string GetCeqExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "=="); + private string GetCleExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "<="); + private string GetCgtExpr(ShaderIrOp Op) => GetBinaryExpr(Op, ">"); + private string GetCneExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "!="); + private string GetCgeExpr(ShaderIrOp Op) => GetBinaryExpr(Op, ">="); + + private string GetExitExpr(ShaderIrOp Op) => "return"; + + private string GetFabsExpr(ShaderIrOp Op) => GetUnaryCall(Op, "abs"); + + private string GetFaddExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "+"); + + private string GetFcosExpr(ShaderIrOp Op) => GetUnaryCall(Op, "cos"); + + private string GetFex2Expr(ShaderIrOp Op) => GetUnaryCall(Op, "exp2"); + + private string GetFfmaExpr(ShaderIrOp Op) => GetTernaryExpr(Op, "*", "+"); + + private string GetFlg2Expr(ShaderIrOp Op) => GetUnaryCall(Op, "log2"); + + private string GetFloorExpr(ShaderIrOp Op) => GetUnaryCall(Op, "floor"); + + private string GetFmulExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "*"); + + private string GetFnegExpr(ShaderIrOp Op) => GetUnaryExpr(Op, "-"); + + private string GetFrcpExpr(ShaderIrOp Op) => GetUnaryExpr(Op, "1 / "); + + private string GetFrsqExpr(ShaderIrOp Op) => GetUnaryCall(Op, "inversesqrt"); + + private string GetFsinExpr(ShaderIrOp Op) => GetUnaryCall(Op, "sin"); + + private string GetFtosExpr(ShaderIrOp Op) + { + return "int(" + GetOperExpr(Op, Op.OperandA) + ")"; + } + + private string GetFtouExpr(ShaderIrOp Op) + { + return "int(uint(" + GetOperExpr(Op, Op.OperandA) + "))"; + } + + private string GetIpaExpr(ShaderIrOp Op) => GetSrcExpr(Op.OperandA); + + private string GetKilExpr(ShaderIrOp Op) => "discard"; + + private string GetLsrExpr(ShaderIrOp Op) + { + return "int(uint(" + GetOperExpr(Op, Op.OperandA) + ") >> " + + GetOperExpr(Op, Op.OperandB) + ")"; + } + + private string GetNotExpr(ShaderIrOp Op) => GetUnaryExpr(Op, "~"); + + private string GetOrExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "|"); + + private string GetStofExpr(ShaderIrOp Op) + { + return "float(" + GetOperExpr(Op, Op.OperandA) + ")"; + } + + private string GetTexqExpr(ShaderIrOp Op) + { + ShaderIrMetaTexq Meta = (ShaderIrMetaTexq)Op.MetaData; + + string Ch = "xyzw".Substring(Meta.Elem, 1); + + if (Meta.Info == ShaderTexqInfo.Dimension) + { + string Sampler = GetTexSamplerName(Op); + + string Lod = GetOperExpr(Op, Op.OperandA); //??? + + return "textureSize(" + Sampler + ", " + Lod + ")." + Ch; + } + else + { + throw new NotImplementedException(Meta.Info.ToString()); + } + } + + private string GetTexsExpr(ShaderIrOp Op) + { + ShaderIrMetaTex Meta = (ShaderIrMetaTex)Op.MetaData; + + string Sampler = GetTexSamplerName(Op); + + string Coords = GetTexSamplerCoords(Op); + + string Ch = "rgba".Substring(Meta.Elem, 1); + + return "texture(" + Sampler + ", " + Coords + ")." + Ch; + } + + private string GetTxlfExpr(ShaderIrOp Op) + { + ShaderIrMetaTex Meta = (ShaderIrMetaTex)Op.MetaData; + + string Sampler = GetTexSamplerName(Op); + + string Coords = GetITexSamplerCoords(Op); + + string Ch = "rgba".Substring(Meta.Elem, 1); + + return "texelFetch(" + Sampler + ", " + Coords + ", 0)." + Ch; + } + + private string GetTruncExpr(ShaderIrOp Op) => GetUnaryCall(Op, "trunc"); + + private string GetUtofExpr(ShaderIrOp Op) + { + return "float(uint(" + GetOperExpr(Op, Op.OperandA) + "))"; + } + + private string GetXorExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "^"); + + private string GetUnaryCall(ShaderIrOp Op, string FuncName) + { + return FuncName + "(" + GetOperExpr(Op, Op.OperandA) + ")"; + } + + private string GetTernaryCall(ShaderIrOp Op, string FuncName) + { + return FuncName + "(" + GetOperExpr(Op, Op.OperandA) + ", " + + GetOperExpr(Op, Op.OperandB) + ", " + + GetOperExpr(Op, Op.OperandC) + ")"; + } + + private string GetUnaryExpr(ShaderIrOp Op, string Opr) + { + return Opr + GetOperExpr(Op, Op.OperandA); + } + + private string GetBinaryExpr(ShaderIrOp Op, string Opr) + { + return GetOperExpr(Op, Op.OperandA) + " " + Opr + " " + + GetOperExpr(Op, Op.OperandB); + } + + private string GetTernaryExpr(ShaderIrOp Op, string Opr1, string Opr2) + { + return GetOperExpr(Op, Op.OperandA) + " " + Opr1 + " " + + GetOperExpr(Op, Op.OperandB) + " " + Opr2 + " " + + GetOperExpr(Op, Op.OperandC); + } + + private string GetTexSamplerName(ShaderIrOp Op) + { + ShaderIrOperImm Node = (ShaderIrOperImm)Op.OperandC; + + int Handle = ((ShaderIrOperImm)Op.OperandC).Value; + + if (!Decl.Textures.TryGetValue(Handle, out ShaderDeclInfo DeclInfo)) + { + throw new InvalidOperationException(); + } + + return DeclInfo.Name; + } + + private string GetTexSamplerCoords(ShaderIrOp Op) + { + return "vec2(" + GetOperExpr(Op, Op.OperandA) + ", " + + GetOperExpr(Op, Op.OperandB) + ")"; + } + + private string GetITexSamplerCoords(ShaderIrOp Op) + { + return "ivec2(" + GetOperExpr(Op, Op.OperandA) + ", " + + GetOperExpr(Op, Op.OperandB) + ")"; + } + + private string GetOperExpr(ShaderIrOp Op, ShaderIrNode Oper) + { + return GetExprWithCast(Op, Oper, GetSrcExpr(Oper)); + } + + private static string GetExprWithCast(ShaderIrNode Dst, ShaderIrNode Src, string Expr) + { + //Note: The "DstType" (of the cast) is the type that the operation + //uses on the source operands, while the "SrcType" is the destination + //type of the operand result (if it is a operation) or just the type + //of the variable for registers/uniforms/attributes. + OperType DstType = GetSrcNodeType(Dst); + OperType SrcType = GetDstNodeType(Src); + + if (DstType != SrcType) + { + //Check for invalid casts + //(like bool to int/float and others). + if (SrcType != OperType.F32 && + SrcType != OperType.I32) + { + throw new InvalidOperationException(); + } + + switch (Src) + { + case ShaderIrOperGpr Gpr: + { + //When the Gpr is ZR, just return the 0 value directly, + //since the float encoding for 0 is 0. + if (Gpr.IsConst) + { + return "0"; + } + break; + } + + case ShaderIrOperImm Imm: + { + //For integer immediates being used as float, + //it's better (for readability) to just return the float value. + if (DstType == OperType.F32) + { + float Value = BitConverter.Int32BitsToSingle(Imm.Value); + + return Value.ToString(CultureInfo.InvariantCulture); + } + break; + } + } + + switch (DstType) + { + case OperType.F32: Expr = "intBitsToFloat(" + Expr + ")"; break; + case OperType.I32: Expr = "floatBitsToInt(" + Expr + ")"; break; + } + } + + return Expr; + } + + private static OperType GetDstNodeType(ShaderIrNode Node) + { + //Special case instructions with the result type different + //from the input types (like integer <-> float conversion) here. + if (Node is ShaderIrOp Op) + { + switch (Op.Inst) + { + case ShaderIrInst.Stof: + case ShaderIrInst.Txlf: + case ShaderIrInst.Utof: + return OperType.F32; + + case ShaderIrInst.Ftos: + case ShaderIrInst.Ftou: + return OperType.I32; + } + } + + return GetSrcNodeType(Node); + } + + private static OperType GetSrcNodeType(ShaderIrNode Node) + { + switch (Node) + { + case ShaderIrOperAbuf Abuf: + return Abuf.Offs == GlslDecl.VertexIdAttr + ? OperType.I32 + : OperType.F32; + + case ShaderIrOperCbuf Cbuf: return OperType.F32; + case ShaderIrOperGpr Gpr: return OperType.F32; + case ShaderIrOperImm Imm: return OperType.I32; + case ShaderIrOperImmf Immf: return OperType.F32; + case ShaderIrOperPred Pred: return OperType.Bool; + + case ShaderIrOp Op: + if (Op.Inst > ShaderIrInst.B_Start && + Op.Inst < ShaderIrInst.B_End) + { + return OperType.Bool; + } + else if (Op.Inst > ShaderIrInst.F_Start && + Op.Inst < ShaderIrInst.F_End) + { + return OperType.F32; + } + else if (Op.Inst > ShaderIrInst.I_Start && + Op.Inst < ShaderIrInst.I_End) + { + return OperType.I32; + } + break; + } + + throw new ArgumentException(nameof(Node)); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/GlslProgram.cs b/Ryujinx.Graphics/Gal/Shader/GlslProgram.cs new file mode 100644 index 000000000..729b6f1de --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/GlslProgram.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Gal.Shader +{ + struct GlslProgram + { + public string Code { get; private set; } + + public IEnumerable Textures { get; private set; } + public IEnumerable Uniforms { get; private set; } + + public GlslProgram( + string Code, + IEnumerable Textures, + IEnumerable Uniforms) + { + this.Code = Code; + this.Textures = Textures; + this.Uniforms = Uniforms; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecode.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecode.cs new file mode 100644 index 000000000..ef0fd78bd --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecode.cs @@ -0,0 +1,4 @@ +namespace Ryujinx.Graphics.Gal.Shader +{ + delegate void ShaderDecodeFunc(ShaderIrBlock Block, long OpCode); +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs new file mode 100644 index 000000000..830aeb8cb --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs @@ -0,0 +1,358 @@ +using System; + +using static Ryujinx.Graphics.Gal.Shader.ShaderDecodeHelper; + +namespace Ryujinx.Graphics.Gal.Shader +{ + static partial class ShaderDecode + { + public static void Fadd_C(ShaderIrBlock Block, long OpCode) + { + EmitAluBinaryF(Block, OpCode, ShaderOper.CR, ShaderIrInst.Fadd); + } + + public static void Fadd_I(ShaderIrBlock Block, long OpCode) + { + EmitAluBinaryF(Block, OpCode, ShaderOper.Immf, ShaderIrInst.Fadd); + } + + public static void Fadd_R(ShaderIrBlock Block, long OpCode) + { + EmitAluBinaryF(Block, OpCode, ShaderOper.RR, ShaderIrInst.Fadd); + } + + public static void Ffma_CR(ShaderIrBlock Block, long OpCode) + { + EmitAluFfma(Block, OpCode, ShaderOper.CR); + } + + public static void Ffma_I(ShaderIrBlock Block, long OpCode) + { + EmitAluFfma(Block, OpCode, ShaderOper.Immf); + } + + public static void Ffma_RC(ShaderIrBlock Block, long OpCode) + { + EmitAluFfma(Block, OpCode, ShaderOper.RC); + } + + public static void Ffma_RR(ShaderIrBlock Block, long OpCode) + { + EmitAluFfma(Block, OpCode, ShaderOper.RR); + } + + public static void Fmul32i(ShaderIrBlock Block, long OpCode) + { + ShaderIrNode OperA = GetOperGpr8 (OpCode); + ShaderIrNode OperB = GetOperImmf32_20(OpCode); + + ShaderIrOp Op = new ShaderIrOp(ShaderIrInst.Fmul, OperA, OperB); + + Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode)); + } + + public static void Fmul_C(ShaderIrBlock Block, long OpCode) + { + EmitAluBinaryF(Block, OpCode, ShaderOper.CR, ShaderIrInst.Fmul); + } + + public static void Fmul_I(ShaderIrBlock Block, long OpCode) + { + EmitAluBinaryF(Block, OpCode, ShaderOper.Immf, ShaderIrInst.Fmul); + } + + public static void Fmul_R(ShaderIrBlock Block, long OpCode) + { + EmitAluBinaryF(Block, OpCode, ShaderOper.RR, ShaderIrInst.Fmul); + } + + public static void Fsetp_C(ShaderIrBlock Block, long OpCode) + { + EmitFsetp(Block, OpCode, ShaderOper.CR); + } + + public static void Fsetp_I(ShaderIrBlock Block, long OpCode) + { + EmitFsetp(Block, OpCode, ShaderOper.Immf); + } + + public static void Fsetp_R(ShaderIrBlock Block, long OpCode) + { + EmitFsetp(Block, OpCode, ShaderOper.RR); + } + + public static void Ipa(ShaderIrBlock Block, long OpCode) + { + ShaderIrNode OperA = GetOperAbuf28(OpCode); + ShaderIrNode OperB = GetOperGpr20 (OpCode); + + ShaderIrOp Op = new ShaderIrOp(ShaderIrInst.Ipa, OperA, OperB); + + Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode)); + } + + public static void Isetp_C(ShaderIrBlock Block, long OpCode) + { + EmitIsetp(Block, OpCode, ShaderOper.CR); + } + + public static void Isetp_I(ShaderIrBlock Block, long OpCode) + { + EmitIsetp(Block, OpCode, ShaderOper.Imm); + } + + public static void Isetp_R(ShaderIrBlock Block, long OpCode) + { + EmitIsetp(Block, OpCode, ShaderOper.RR); + } + + public static void Lop32i(ShaderIrBlock Block, long OpCode) + { + int SubOp = (int)(OpCode >> 53) & 3; + + bool Ia = ((OpCode >> 55) & 1) != 0; + bool Ib = ((OpCode >> 56) & 1) != 0; + + ShaderIrInst Inst = 0; + + switch (SubOp) + { + case 0: Inst = ShaderIrInst.And; break; + case 1: Inst = ShaderIrInst.Or; break; + case 2: Inst = ShaderIrInst.Xor; break; + } + + ShaderIrNode OperA = GetAluNot(GetOperGpr8(OpCode), Ia); + + //SubOp == 3 is pass, used by the not instruction + //which just moves the inverted register value. + if (SubOp < 3) + { + ShaderIrNode OperB = GetAluNot(GetOperImm32_20(OpCode), Ib); + + ShaderIrOp Op = new ShaderIrOp(Inst, OperA, OperB); + + Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode)); + } + else + { + Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), OperA), OpCode)); + } + } + + public static void Mufu(ShaderIrBlock Block, long OpCode) + { + int SubOp = (int)(OpCode >> 20) & 7; + + bool Aa = ((OpCode >> 46) & 1) != 0; + bool Na = ((OpCode >> 48) & 1) != 0; + + ShaderIrInst Inst = 0; + + switch (SubOp) + { + case 0: Inst = ShaderIrInst.Fcos; break; + case 1: Inst = ShaderIrInst.Fsin; break; + case 2: Inst = ShaderIrInst.Fex2; break; + case 3: Inst = ShaderIrInst.Flg2; break; + case 4: Inst = ShaderIrInst.Frcp; break; + case 5: Inst = ShaderIrInst.Frsq; break; + + default: throw new NotImplementedException(SubOp.ToString()); + } + + ShaderIrNode OperA = GetOperGpr8(OpCode); + + ShaderIrOp Op = new ShaderIrOp(Inst, GetAluAbsNeg(OperA, Aa, Na)); + + Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode)); + } + + public static void Shr_C(ShaderIrBlock Block, long OpCode) + { + EmitAluBinary(Block, OpCode, ShaderOper.CR, GetShrInst(OpCode)); + } + + public static void Shr_I(ShaderIrBlock Block, long OpCode) + { + EmitAluBinary(Block, OpCode, ShaderOper.Imm, GetShrInst(OpCode)); + } + + public static void Shr_R(ShaderIrBlock Block, long OpCode) + { + EmitAluBinary(Block, OpCode, ShaderOper.RR, GetShrInst(OpCode)); + } + + private static ShaderIrInst GetShrInst(long OpCode) + { + bool Signed = ((OpCode >> 48) & 1) != 0; + + return Signed ? ShaderIrInst.Asr : ShaderIrInst.Lsr; + } + + private static void EmitAluBinary( + ShaderIrBlock Block, + long OpCode, + ShaderOper Oper, + ShaderIrInst Inst) + { + ShaderIrNode OperA = GetOperGpr8(OpCode), OperB; + + switch (Oper) + { + case ShaderOper.CR: OperB = GetOperCbuf34 (OpCode); break; + case ShaderOper.Imm: OperB = GetOperImm19_20(OpCode); break; + case ShaderOper.RR: OperB = GetOperGpr20 (OpCode); break; + + default: throw new ArgumentException(nameof(Oper)); + } + + ShaderIrNode Op = new ShaderIrOp(Inst, OperA, OperB); + + Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode)); + } + + private static void EmitAluBinaryF( + ShaderIrBlock Block, + long OpCode, + ShaderOper Oper, + ShaderIrInst Inst) + { + bool Nb = ((OpCode >> 45) & 1) != 0; + bool Aa = ((OpCode >> 46) & 1) != 0; + bool Na = ((OpCode >> 48) & 1) != 0; + bool Ab = ((OpCode >> 49) & 1) != 0; + + ShaderIrNode OperA = GetOperGpr8(OpCode), OperB; + + if (Inst == ShaderIrInst.Fadd) + { + OperA = GetAluAbsNeg(OperA, Aa, Na); + } + + switch (Oper) + { + case ShaderOper.CR: OperB = GetOperCbuf34 (OpCode); break; + case ShaderOper.Immf: OperB = GetOperImmf19_20(OpCode); break; + case ShaderOper.RR: OperB = GetOperGpr20 (OpCode); break; + + default: throw new ArgumentException(nameof(Oper)); + } + + OperB = GetAluAbsNeg(OperB, Ab, Nb); + + ShaderIrNode Op = new ShaderIrOp(Inst, OperA, OperB); + + Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode)); + } + + private static void EmitAluFfma(ShaderIrBlock Block, long OpCode, ShaderOper Oper) + { + bool Nb = ((OpCode >> 48) & 1) != 0; + bool Nc = ((OpCode >> 49) & 1) != 0; + + ShaderIrNode OperA = GetOperGpr8(OpCode), OperB, OperC; + + switch (Oper) + { + case ShaderOper.CR: OperB = GetOperCbuf34 (OpCode); break; + case ShaderOper.Immf: OperB = GetOperImmf19_20(OpCode); break; + case ShaderOper.RC: OperB = GetOperGpr39 (OpCode); break; + case ShaderOper.RR: OperB = GetOperGpr20 (OpCode); break; + + default: throw new ArgumentException(nameof(Oper)); + } + + OperB = GetAluNeg(OperB, Nb); + + if (Oper == ShaderOper.RC) + { + OperC = GetAluNeg(GetOperCbuf34(OpCode), Nc); + } + else + { + OperC = GetAluNeg(GetOperGpr39(OpCode), Nc); + } + + ShaderIrOp Op = new ShaderIrOp(ShaderIrInst.Ffma, OperA, OperB, OperC); + + Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode)); + } + + private static void EmitFsetp(ShaderIrBlock Block, long OpCode, ShaderOper Oper) + { + EmitSetp(Block, OpCode, true, Oper); + } + + private static void EmitIsetp(ShaderIrBlock Block, long OpCode, ShaderOper Oper) + { + EmitSetp(Block, OpCode, false, Oper); + } + + private static void EmitSetp(ShaderIrBlock Block, long OpCode, bool IsFloat, ShaderOper Oper) + { + bool Aa = ((OpCode >> 7) & 1) != 0; + bool Np = ((OpCode >> 42) & 1) != 0; + bool Na = ((OpCode >> 43) & 1) != 0; + bool Ab = ((OpCode >> 44) & 1) != 0; + + ShaderIrNode OperA = GetOperGpr8(OpCode), OperB; + + switch (Oper) + { + case ShaderOper.CR: OperB = GetOperCbuf34 (OpCode); break; + case ShaderOper.Imm: OperB = GetOperImm19_20 (OpCode); break; + case ShaderOper.Immf: OperB = GetOperImmf19_20(OpCode); break; + case ShaderOper.RR: OperB = GetOperGpr20 (OpCode); break; + + default: throw new ArgumentException(nameof(Oper)); + } + + ShaderIrInst CmpInst; + + if (IsFloat) + { + OperA = GetAluAbsNeg(OperA, Aa, Na); + OperB = GetAluAbs (OperB, Ab); + + CmpInst = GetCmpF(OpCode); + } + else + { + CmpInst = GetCmp(OpCode); + } + + ShaderIrOp Op = new ShaderIrOp(CmpInst, OperA, OperB); + + ShaderIrOperPred P0Node = GetOperPred3 (OpCode); + ShaderIrOperPred P1Node = GetOperPred0 (OpCode); + ShaderIrOperPred P2Node = GetOperPred39(OpCode); + + Block.AddNode(GetPredNode(new ShaderIrAsg(P0Node, Op), OpCode)); + + ShaderIrInst LopInst = GetBLop(OpCode); + + if (LopInst == ShaderIrInst.Band && P1Node.IsConst && P2Node.IsConst) + { + return; + } + + ShaderIrNode P2NNode = P2Node; + + if (Np) + { + P2NNode = new ShaderIrOp(ShaderIrInst.Bnot, P2NNode); + } + + Op = new ShaderIrOp(ShaderIrInst.Bnot, P0Node); + + Op = new ShaderIrOp(LopInst, Op, P2NNode); + + Block.AddNode(GetPredNode(new ShaderIrAsg(P1Node, Op), OpCode)); + + Op = new ShaderIrOp(LopInst, P0Node, P2NNode); + + Block.AddNode(GetPredNode(new ShaderIrAsg(P0Node, Op), OpCode)); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeFlow.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeFlow.cs new file mode 100644 index 000000000..d3feb92e5 --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeFlow.cs @@ -0,0 +1,17 @@ +using static Ryujinx.Graphics.Gal.Shader.ShaderDecodeHelper; + +namespace Ryujinx.Graphics.Gal.Shader +{ + static partial class ShaderDecode + { + public static void Exit(ShaderIrBlock Block, long OpCode) + { + Block.AddNode(GetPredNode(new ShaderIrOp(ShaderIrInst.Exit), OpCode)); + } + + public static void Kil(ShaderIrBlock Block, long OpCode) + { + Block.AddNode(GetPredNode(new ShaderIrOp(ShaderIrInst.Kil), OpCode)); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeHelper.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeHelper.cs new file mode 100644 index 000000000..efbce20f2 --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeHelper.cs @@ -0,0 +1,233 @@ +using System; + +namespace Ryujinx.Graphics.Gal.Shader +{ + static class ShaderDecodeHelper + { + public static ShaderIrOperAbuf[] GetOperAbuf20(long OpCode) + { + int Abuf = (int)(OpCode >> 20) & 0x3ff; + int Reg = (int)(OpCode >> 39) & 0xff; + int Size = (int)(OpCode >> 47) & 3; + + ShaderIrOperAbuf[] Opers = new ShaderIrOperAbuf[Size + 1]; + + for (int Index = 0; Index <= Size; Index++) + { + Opers[Index] = new ShaderIrOperAbuf(Abuf, Reg); + } + + return Opers; + } + + public static ShaderIrOperAbuf GetOperAbuf28(long OpCode) + { + int Abuf = (int)(OpCode >> 28) & 0x3ff; + int Reg = (int)(OpCode >> 39) & 0xff; + + return new ShaderIrOperAbuf(Abuf, Reg); + } + + public static ShaderIrOperCbuf GetOperCbuf34(long OpCode) + { + return new ShaderIrOperCbuf( + (int)(OpCode >> 34) & 0x1f, + (int)(OpCode >> 20) & 0x3fff); + } + + public static ShaderIrOperGpr GetOperGpr8(long OpCode) + { + return new ShaderIrOperGpr((int)(OpCode >> 8) & 0xff); + } + + public static ShaderIrOperGpr GetOperGpr20(long OpCode) + { + return new ShaderIrOperGpr((int)(OpCode >> 20) & 0xff); + } + + public static ShaderIrOperGpr GetOperGpr39(long OpCode) + { + return new ShaderIrOperGpr((int)(OpCode >> 39) & 0xff); + } + + public static ShaderIrOperGpr GetOperGpr0(long OpCode) + { + return new ShaderIrOperGpr((int)(OpCode >> 0) & 0xff); + } + + public static ShaderIrOperGpr GetOperGpr28(long OpCode) + { + return new ShaderIrOperGpr((int)(OpCode >> 28) & 0xff); + } + + public static ShaderIrOperImm GetOperImm13_36(long OpCode) + { + return new ShaderIrOperImm((int)(OpCode >> 36) & 0x1fff); + } + + public static ShaderIrOperImm GetOperImm32_20(long OpCode) + { + return new ShaderIrOperImm((int)(OpCode >> 20)); + } + + public static ShaderIrOperImmf GetOperImmf32_20(long OpCode) + { + return new ShaderIrOperImmf(BitConverter.Int32BitsToSingle((int)(OpCode >> 20))); + } + + public static ShaderIrOperImm GetOperImm19_20(long OpCode) + { + int Value = (int)(OpCode >> 20) & 0x7ffff; + + bool Neg = ((OpCode >> 56) & 1) != 0; + + if (Neg) + { + Value = -Value; + } + + return new ShaderIrOperImm((int)Value); + } + + public static ShaderIrOperImmf GetOperImmf19_20(long OpCode) + { + uint Imm = (uint)(OpCode >> 20) & 0x7ffff; + + bool Neg = ((OpCode >> 56) & 1) != 0; + + Imm <<= 12; + + if (Neg) + { + Imm |= 0x80000000; + } + + float Value = BitConverter.Int32BitsToSingle((int)Imm); + + return new ShaderIrOperImmf(Value); + } + + public static ShaderIrOperPred GetOperPred3(long OpCode) + { + return new ShaderIrOperPred((int)(OpCode >> 3) & 7); + } + + public static ShaderIrOperPred GetOperPred0(long OpCode) + { + return new ShaderIrOperPred((int)(OpCode >> 0) & 7); + } + + public static ShaderIrNode GetOperPred39N(long OpCode) + { + ShaderIrNode Node = GetOperPred39(OpCode); + + if (((OpCode >> 42) & 1) != 0) + { + Node = new ShaderIrOp(ShaderIrInst.Bnot, Node); + } + + return Node; + } + + public static ShaderIrOperPred GetOperPred39(long OpCode) + { + return new ShaderIrOperPred((int)(OpCode >> 39) & 7); + } + + public static ShaderIrInst GetCmp(long OpCode) + { + switch ((int)(OpCode >> 49) & 7) + { + case 1: return ShaderIrInst.Clt; + case 2: return ShaderIrInst.Ceq; + case 3: return ShaderIrInst.Cle; + case 4: return ShaderIrInst.Cgt; + case 5: return ShaderIrInst.Cne; + case 6: return ShaderIrInst.Cge; + } + + throw new ArgumentException(nameof(OpCode)); + } + + public static ShaderIrInst GetCmpF(long OpCode) + { + switch ((int)(OpCode >> 48) & 0xf) + { + case 0x1: return ShaderIrInst.Fclt; + case 0x2: return ShaderIrInst.Fceq; + case 0x3: return ShaderIrInst.Fcle; + case 0x4: return ShaderIrInst.Fcgt; + case 0x5: return ShaderIrInst.Fcne; + case 0x6: return ShaderIrInst.Fcge; + case 0x7: return ShaderIrInst.Fcnum; + case 0x8: return ShaderIrInst.Fcnan; + case 0x9: return ShaderIrInst.Fcltu; + case 0xa: return ShaderIrInst.Fcequ; + case 0xb: return ShaderIrInst.Fcleu; + case 0xc: return ShaderIrInst.Fcgtu; + case 0xd: return ShaderIrInst.Fcneu; + case 0xe: return ShaderIrInst.Fcgeu; + } + + throw new ArgumentException(nameof(OpCode)); + } + + public static ShaderIrInst GetBLop(long OpCode) + { + switch ((int)(OpCode >> 45) & 3) + { + case 0: return ShaderIrInst.Band; + case 1: return ShaderIrInst.Bor; + case 2: return ShaderIrInst.Bxor; + } + + throw new ArgumentException(nameof(OpCode)); + } + + public static ShaderIrNode GetPredNode(ShaderIrNode Node, long OpCode) + { + ShaderIrOperPred Pred = GetPredNode(OpCode); + + if (Pred.Index != ShaderIrOperPred.UnusedIndex) + { + bool Inv = ((OpCode >> 19) & 1) != 0; + + Node = new ShaderIrCond(Pred, Node, Inv); + } + + return Node; + } + + private static ShaderIrOperPred GetPredNode(long OpCode) + { + int Pred = (int)(OpCode >> 16) & 0xf; + + if (Pred != 0xf) + { + Pred &= 7; + } + + return new ShaderIrOperPred(Pred); + } + + public static ShaderIrNode GetAluAbsNeg(ShaderIrNode Node, bool Abs, bool Neg) + { + return GetAluNeg(GetAluAbs(Node, Abs), Neg); + } + + public static ShaderIrNode GetAluAbs(ShaderIrNode Node, bool Abs) + { + return Abs ? new ShaderIrOp(ShaderIrInst.Fabs, Node) : Node; + } + + public static ShaderIrNode GetAluNeg(ShaderIrNode Node, bool Neg) + { + return Neg ? new ShaderIrOp(ShaderIrInst.Fneg, Node) : Node; + } + + public static ShaderIrNode GetAluNot(ShaderIrNode Node, bool Not) + { + return Not ? new ShaderIrOp(ShaderIrInst.Not, Node) : Node; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMem.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMem.cs new file mode 100644 index 000000000..33f582316 --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMem.cs @@ -0,0 +1,102 @@ +using static Ryujinx.Graphics.Gal.Shader.ShaderDecodeHelper; + +namespace Ryujinx.Graphics.Gal.Shader +{ + static partial class ShaderDecode + { + public static void Ld_A(ShaderIrBlock Block, long OpCode) + { + ShaderIrNode[] Opers = GetOperAbuf20(OpCode); + + int Index = 0; + + foreach (ShaderIrNode OperA in Opers) + { + ShaderIrOperGpr OperD = GetOperGpr0(OpCode); + + OperD.Index += Index++; + + Block.AddNode(GetPredNode(new ShaderIrAsg(OperD, OperA), OpCode)); + } + } + + public static void St_A(ShaderIrBlock Block, long OpCode) + { + ShaderIrNode[] Opers = GetOperAbuf20(OpCode); + + int Index = 0; + + foreach (ShaderIrNode OperA in Opers) + { + ShaderIrOperGpr OperD = GetOperGpr0(OpCode); + + OperD.Index += Index++; + + Block.AddNode(GetPredNode(new ShaderIrAsg(OperA, OperD), OpCode)); + } + } + + public static void Texq(ShaderIrBlock Block, long OpCode) + { + ShaderIrNode OperD = GetOperGpr0(OpCode); + ShaderIrNode OperA = GetOperGpr8(OpCode); + + ShaderTexqInfo Info = (ShaderTexqInfo)((OpCode >> 22) & 0x1f); + + ShaderIrMetaTexq Meta0 = new ShaderIrMetaTexq(Info, 0); + ShaderIrMetaTexq Meta1 = new ShaderIrMetaTexq(Info, 1); + + ShaderIrNode OperC = GetOperImm13_36(OpCode); + + ShaderIrOp Op0 = new ShaderIrOp(ShaderIrInst.Texq, OperA, null, OperC, Meta0); + ShaderIrOp Op1 = new ShaderIrOp(ShaderIrInst.Texq, OperA, null, OperC, Meta1); + + Block.AddNode(GetPredNode(new ShaderIrAsg(OperD, Op0), OpCode)); + Block.AddNode(GetPredNode(new ShaderIrAsg(OperA, Op1), OpCode)); //Is this right? + } + + public static void Texs(ShaderIrBlock Block, long OpCode) + { + EmitTex(Block, OpCode, ShaderIrInst.Texs); + } + + public static void Tlds(ShaderIrBlock Block, long OpCode) + { + EmitTex(Block, OpCode, ShaderIrInst.Txlf); + } + + private static void EmitTex(ShaderIrBlock Block, long OpCode, ShaderIrInst Inst) + { + //TODO: Support other formats. + ShaderIrNode OperA = GetOperGpr8 (OpCode); + ShaderIrNode OperB = GetOperGpr20 (OpCode); + ShaderIrNode OperC = GetOperImm13_36(OpCode); + + for (int Ch = 0; Ch < 4; Ch++) + { + //Assign it to a temp because the destination registers + //may be used as texture coord input aswell. + ShaderIrOperGpr Dst = new ShaderIrOperGpr(0x100 + Ch); + + ShaderIrMetaTex Meta = new ShaderIrMetaTex(Ch); + + ShaderIrOp Op = new ShaderIrOp(Inst, OperA, OperB, OperC, Meta); + + Block.AddNode(GetPredNode(new ShaderIrAsg(Dst, Op), OpCode)); + } + + for (int Ch = 0; Ch < 4; Ch++) + { + ShaderIrOperGpr Src = new ShaderIrOperGpr(0x100 + Ch); + + ShaderIrOperGpr Dst = (Ch >> 1) != 0 + ? GetOperGpr28(OpCode) + : GetOperGpr0 (OpCode); + + Dst.Index += Ch & 1; + + Block.AddNode(GetPredNode(new ShaderIrAsg(Dst, Src), OpCode)); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMove.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMove.cs new file mode 100644 index 000000000..6d30cfed8 --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMove.cs @@ -0,0 +1,286 @@ +using System; + +using static Ryujinx.Graphics.Gal.Shader.ShaderDecodeHelper; + +namespace Ryujinx.Graphics.Gal.Shader +{ + static partial class ShaderDecode + { + private enum IntType + { + U8 = 0, + U16 = 1, + U32 = 2, + U64 = 3, + S8 = 4, + S16 = 5, + S32 = 6, + S64 = 7 + } + + private enum FloatType + { + F16 = 1, + F32 = 2, + F64 = 3 + } + + public static void F2f_C(ShaderIrBlock Block, long OpCode) + { + EmitF2f(Block, OpCode, ShaderOper.CR); + } + + public static void F2f_I(ShaderIrBlock Block, long OpCode) + { + EmitF2f(Block, OpCode, ShaderOper.Immf); + } + + public static void F2f_R(ShaderIrBlock Block, long OpCode) + { + EmitF2f(Block, OpCode, ShaderOper.RR); + } + + public static void F2i_C(ShaderIrBlock Block, long OpCode) + { + EmitF2i(Block, OpCode, ShaderOper.CR); + } + + public static void F2i_I(ShaderIrBlock Block, long OpCode) + { + EmitF2i(Block, OpCode, ShaderOper.Immf); + } + + public static void F2i_R(ShaderIrBlock Block, long OpCode) + { + EmitF2i(Block, OpCode, ShaderOper.RR); + } + + public static void I2f_C(ShaderIrBlock Block, long OpCode) + { + EmitI2f(Block, OpCode, ShaderOper.CR); + } + + public static void I2f_I(ShaderIrBlock Block, long OpCode) + { + EmitI2f(Block, OpCode, ShaderOper.Imm); + } + + public static void I2f_R(ShaderIrBlock Block, long OpCode) + { + EmitI2f(Block, OpCode, ShaderOper.RR); + } + + public static void Mov_C(ShaderIrBlock Block, long OpCode) + { + ShaderIrOperCbuf Cbuf = GetOperCbuf34(OpCode); + + Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Cbuf), OpCode)); + } + + public static void Mov_I(ShaderIrBlock Block, long OpCode) + { + ShaderIrOperImm Imm = GetOperImm19_20(OpCode); + + Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Imm), OpCode)); + } + + public static void Mov_R(ShaderIrBlock Block, long OpCode) + { + ShaderIrOperGpr Gpr = GetOperGpr20(OpCode); + + Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Gpr), OpCode)); + } + + public static void Mov32i(ShaderIrBlock Block, long OpCode) + { + ShaderIrOperImm Imm = GetOperImm32_20(OpCode); + + Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Imm), OpCode)); + } + + private static void EmitF2f(ShaderIrBlock Block, long OpCode, ShaderOper Oper) + { + bool Na = ((OpCode >> 45) & 1) != 0; + bool Aa = ((OpCode >> 49) & 1) != 0; + + ShaderIrNode OperA; + + switch (Oper) + { + case ShaderOper.CR: OperA = GetOperCbuf34 (OpCode); break; + case ShaderOper.Immf: OperA = GetOperImmf19_20(OpCode); break; + case ShaderOper.RR: OperA = GetOperGpr20 (OpCode); break; + + default: throw new ArgumentException(nameof(Oper)); + } + + OperA = GetAluAbsNeg(OperA, Aa, Na); + + ShaderIrInst RoundInst = GetRoundInst(OpCode); + + if (RoundInst != ShaderIrInst.Invalid) + { + OperA = new ShaderIrOp(RoundInst, OperA); + } + + Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), OperA), OpCode)); + } + + private static void EmitF2i(ShaderIrBlock Block, long OpCode, ShaderOper Oper) + { + IntType Type = GetIntType(OpCode); + + if (Type == IntType.U64 || + Type == IntType.S64) + { + //TODO: 64-bits support. + //Note: GLSL doesn't support 64-bits integers. + throw new NotImplementedException(); + } + + bool Na = ((OpCode >> 45) & 1) != 0; + bool Aa = ((OpCode >> 49) & 1) != 0; + + ShaderIrNode OperA; + + switch (Oper) + { + case ShaderOper.CR: OperA = GetOperCbuf34 (OpCode); break; + case ShaderOper.Immf: OperA = GetOperImmf19_20(OpCode); break; + case ShaderOper.RR: OperA = GetOperGpr20 (OpCode); break; + + default: throw new ArgumentException(nameof(Oper)); + } + + OperA = GetAluAbsNeg(OperA, Aa, Na); + + ShaderIrInst RoundInst = GetRoundInst(OpCode); + + if (RoundInst != ShaderIrInst.Invalid) + { + OperA = new ShaderIrOp(RoundInst, OperA); + } + + bool Signed = Type >= IntType.S8; + + int Size = 8 << ((int)Type & 3); + + if (Size < 32) + { + uint Mask = uint.MaxValue >> (32 - Size); + + float CMin = 0; + float CMax = Mask; + + if (Signed) + { + uint HalfMask = Mask >> 1; + + CMin -= HalfMask + 1; + CMax = HalfMask; + } + + ShaderIrOperImmf IMin = new ShaderIrOperImmf(CMin); + ShaderIrOperImmf IMax = new ShaderIrOperImmf(CMax); + + OperA = new ShaderIrOp(ShaderIrInst.Clamp, OperA, IMin, IMax); + } + + ShaderIrInst Inst = Signed + ? ShaderIrInst.Ftos + : ShaderIrInst.Ftou; + + ShaderIrNode Op = new ShaderIrOp(Inst, OperA); + + Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode)); + } + + private static void EmitI2f(ShaderIrBlock Block, long OpCode, ShaderOper Oper) + { + IntType Type = GetIntType(OpCode); + + if (Type == IntType.U64 || + Type == IntType.S64) + { + //TODO: 64-bits support. + //Note: GLSL doesn't support 64-bits integers. + throw new NotImplementedException(); + } + + int Sel = (int)(OpCode >> 41) & 3; + + bool Na = ((OpCode >> 45) & 1) != 0; + bool Aa = ((OpCode >> 49) & 1) != 0; + + ShaderIrNode OperA; + + switch (Oper) + { + case ShaderOper.CR: OperA = GetOperCbuf34 (OpCode); break; + case ShaderOper.Imm: OperA = GetOperImm19_20(OpCode); break; + case ShaderOper.RR: OperA = GetOperGpr20 (OpCode); break; + + default: throw new ArgumentException(nameof(Oper)); + } + + OperA = GetAluAbsNeg(OperA, Aa, Na); + + bool Signed = Type >= IntType.S8; + + int Shift = Sel * 8; + + int Size = 8 << ((int)Type & 3); + + if (Shift != 0) + { + OperA = new ShaderIrOp(ShaderIrInst.Asr, OperA, new ShaderIrOperImm(Shift)); + } + + if (Size < 32) + { + uint Mask = uint.MaxValue >> (32 - Size); + + OperA = new ShaderIrOp(ShaderIrInst.And, OperA, new ShaderIrOperImm((int)Mask)); + } + + ShaderIrInst Inst = Signed + ? ShaderIrInst.Stof + : ShaderIrInst.Utof; + + ShaderIrNode Op = new ShaderIrOp(Inst, OperA); + + Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode)); + } + + private static IntType GetIntType(long OpCode) + { + bool Signed = ((OpCode >> 13) & 1) != 0; + + IntType Type = (IntType)((OpCode >> 10) & 3); + + if (Signed) + { + Type += (int)IntType.S8; + } + + return Type; + } + + private static FloatType GetFloatType(long OpCode) + { + return (FloatType)((OpCode >> 8) & 3); + } + + private static ShaderIrInst GetRoundInst(long OpCode) + { + switch ((OpCode >> 39) & 3) + { + case 1: return ShaderIrInst.Floor; + case 2: return ShaderIrInst.Ceil; + case 3: return ShaderIrInst.Trunc; + } + + return ShaderIrInst.Invalid; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecoder.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecoder.cs new file mode 100644 index 000000000..e44d5b7d7 --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecoder.cs @@ -0,0 +1,48 @@ +namespace Ryujinx.Graphics.Gal.Shader +{ + static class ShaderDecoder + { + public static ShaderIrBlock DecodeBasicBlock(int[] Code, int Offset) + { + ShaderIrBlock Block = new ShaderIrBlock(); + + while (Offset + 2 <= Code.Length) + { + //Ignore scheduling instructions, which are + //written every 32 bytes. + if ((Offset & 7) == 0) + { + Offset += 2; + + continue; + } + + uint Word0 = (uint)Code[Offset++]; + uint Word1 = (uint)Code[Offset++]; + + long OpCode = Word0 | (long)Word1 << 32; + + ShaderDecodeFunc Decode = ShaderOpCodeTable.GetDecoder(OpCode); + + if (Decode == null) + { + continue; + } + + Decode(Block, OpCode); + + if (Block.GetLastNode() is ShaderIrOp Op && IsFlowChange(Op.Inst)) + { + break; + } + } + + return Block; + } + + private static bool IsFlowChange(ShaderIrInst Inst) + { + return Inst == ShaderIrInst.Exit; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrAsg.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrAsg.cs new file mode 100644 index 000000000..00f8f6a5e --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrAsg.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.Graphics.Gal.Shader +{ + class ShaderIrAsg : ShaderIrNode + { + public ShaderIrNode Dst { get; set; } + public ShaderIrNode Src { get; set; } + + public ShaderIrAsg(ShaderIrNode Dst, ShaderIrNode Src) + { + this.Dst = Dst; + this.Src = Src; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrBlock.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrBlock.cs new file mode 100644 index 000000000..c920d9fa5 --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrBlock.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Gal.Shader +{ + class ShaderIrBlock + { + private List Nodes; + + public ShaderIrBlock() + { + Nodes = new List(); + } + + public void AddNode(ShaderIrNode Node) + { + Nodes.Add(Node); + } + + public ShaderIrNode[] GetNodes() + { + return Nodes.ToArray(); + } + + public ShaderIrNode GetLastNode() + { + if (Nodes.Count > 0) + { + return Nodes[Nodes.Count - 1]; + } + + return null; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrCond.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrCond.cs new file mode 100644 index 000000000..8fb01660c --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrCond.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Graphics.Gal.Shader +{ + class ShaderIrCond : ShaderIrNode + { + public ShaderIrNode Pred { get; set; } + public ShaderIrNode Child { get; set; } + + public bool Not { get; private set; } + + public ShaderIrCond(ShaderIrNode Pred, ShaderIrNode Child, bool Not) + { + this.Pred = Pred; + this.Child = Child; + this.Not = Not; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrInst.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrInst.cs new file mode 100644 index 000000000..1b72f6476 --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrInst.cs @@ -0,0 +1,72 @@ +namespace Ryujinx.Graphics.Gal.Shader +{ + enum ShaderIrInst + { + Invalid, + + B_Start, + Band, + Bnot, + Bor, + Bxor, + B_End, + + F_Start, + Ceil, + Clamp, + Fabs, + Fadd, + Fceq, + Fcequ, + Fcge, + Fcgeu, + Fcgt, + Fcgtu, + Fcle, + Fcleu, + Fclt, + Fcltu, + Fcnan, + Fcne, + Fcneu, + Fcnum, + Fcos, + Fex2, + Ffma, + Flg2, + Floor, + Fmul, + Fneg, + Frcp, + Frsq, + Fsin, + Ftos, + Ftou, + Ipa, + Texs, + Trunc, + F_End, + + I_Start, + And, + Asr, + Ceq, + Cge, + Cgt, + Cle, + Clt, + Cne, + Lsr, + Not, + Or, + Stof, + Texq, + Txlf, + Utof, + Xor, + I_End, + + Exit, + Kil + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrMeta.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrMeta.cs new file mode 100644 index 000000000..afb7503be --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrMeta.cs @@ -0,0 +1,4 @@ +namespace Ryujinx.Graphics.Gal.Shader +{ + class ShaderIrMeta { } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrMetaTex.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrMetaTex.cs new file mode 100644 index 000000000..82f3bb774 --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrMetaTex.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Gal.Shader +{ + class ShaderIrMetaTex : ShaderIrMeta + { + public int Elem { get; private set; } + + public ShaderIrMetaTex(int Elem) + { + this.Elem = Elem; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrMetaTexq.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrMetaTexq.cs new file mode 100644 index 000000000..92871137f --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrMetaTexq.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Graphics.Gal.Shader +{ + class ShaderIrMetaTexq : ShaderIrMeta + { + public ShaderTexqInfo Info { get; private set; } + + public int Elem { get; private set; } + + public ShaderIrMetaTexq(ShaderTexqInfo Info, int Elem) + { + this.Info = Info; + this.Elem = Elem; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrNode.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrNode.cs new file mode 100644 index 000000000..2648164a1 --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrNode.cs @@ -0,0 +1,4 @@ +namespace Ryujinx.Graphics.Gal.Shader +{ + class ShaderIrNode { } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOp.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOp.cs new file mode 100644 index 000000000..12a6123c3 --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrOp.cs @@ -0,0 +1,25 @@ +namespace Ryujinx.Graphics.Gal.Shader +{ + class ShaderIrOp : ShaderIrNode + { + public ShaderIrInst Inst { get; private set; } + public ShaderIrNode OperandA { get; set; } + public ShaderIrNode OperandB { get; set; } + public ShaderIrNode OperandC { get; set; } + public ShaderIrMeta MetaData { get; set; } + + public ShaderIrOp( + ShaderIrInst Inst, + ShaderIrNode OperandA = null, + ShaderIrNode OperandB = null, + ShaderIrNode OperandC = null, + ShaderIrMeta MetaData = null) + { + this.Inst = Inst; + this.OperandA = OperandA; + this.OperandB = OperandB; + this.OperandC = OperandC; + this.MetaData = MetaData; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperAbuf.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperAbuf.cs new file mode 100644 index 000000000..fa612de76 --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperAbuf.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.Graphics.Gal.Shader +{ + class ShaderIrOperAbuf : ShaderIrNode + { + public int Offs { get; private set; } + public int GprIndex { get; private set; } + + public ShaderIrOperAbuf(int Offs, int GprIndex) + { + this.Offs = Offs; + this.GprIndex = GprIndex; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperCbuf.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperCbuf.cs new file mode 100644 index 000000000..f22720563 --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperCbuf.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.Graphics.Gal.Shader +{ + class ShaderIrOperCbuf : ShaderIrNode + { + public int Index { get; private set; } + public int Offs { get; private set; } + + public ShaderIrOperCbuf(int Index, int Offs) + { + this.Index = Index; + this.Offs = Offs; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperGpr.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperGpr.cs new file mode 100644 index 000000000..5c69d6a67 --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperGpr.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.Graphics.Gal.Shader +{ + class ShaderIrOperGpr : ShaderIrNode + { + public const int ZRIndex = 0xff; + + public bool IsConst => Index == ZRIndex; + + public int Index { get; set; } + + public ShaderIrOperGpr(int Index) + { + this.Index = Index; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperImm.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperImm.cs new file mode 100644 index 000000000..ba2c2c9b2 --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperImm.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Gal.Shader +{ + class ShaderIrOperImm : ShaderIrNode + { + public int Value { get; private set; } + + public ShaderIrOperImm(int Value) + { + this.Value = Value; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperImmf.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperImmf.cs new file mode 100644 index 000000000..3c27e4836 --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperImmf.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Gal.Shader +{ + class ShaderIrOperImmf : ShaderIrNode + { + public float Value { get; private set; } + + public ShaderIrOperImmf(float Value) + { + this.Value = Value; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperPred.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperPred.cs new file mode 100644 index 000000000..74cca0efe --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperPred.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Graphics.Gal.Shader +{ + class ShaderIrOperPred : ShaderIrNode + { + public const int UnusedIndex = 0x7; + public const int NeverExecute = 0xf; + + public bool IsConst => Index >= UnusedIndex; + + public int Index { get; set; } + + public ShaderIrOperPred(int Index) + { + this.Index = Index; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs b/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs new file mode 100644 index 000000000..762544cb9 --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs @@ -0,0 +1,112 @@ +using System; + +namespace Ryujinx.Graphics.Gal.Shader +{ + static class ShaderOpCodeTable + { + private const int EncodingBits = 14; + + private static ShaderDecodeFunc[] OpCodes; + + static ShaderOpCodeTable() + { + OpCodes = new ShaderDecodeFunc[1 << EncodingBits]; + +#region Instructions + Set("111000110000xx", ShaderDecode.Exit); + Set("0100110010101x", ShaderDecode.F2f_C); + Set("0011100x10101x", ShaderDecode.F2f_I); + Set("0101110010101x", ShaderDecode.F2f_R); + Set("0100110010110x", ShaderDecode.F2i_C); + Set("0011100x10110x", ShaderDecode.F2i_I); + Set("0101110010110x", ShaderDecode.F2i_R); + Set("0100110001011x", ShaderDecode.Fadd_C); + Set("0011100x01011x", ShaderDecode.Fadd_I); + Set("0101110001011x", ShaderDecode.Fadd_R); + Set("010010011xxxxx", ShaderDecode.Ffma_CR); + Set("001100101xxxxx", ShaderDecode.Ffma_I); + Set("010100011xxxxx", ShaderDecode.Ffma_RC); + Set("010110011xxxxx", ShaderDecode.Ffma_RR); + Set("00011110xxxxxx", ShaderDecode.Fmul32i); + Set("0100110001101x", ShaderDecode.Fmul_C); + Set("0011100x01101x", ShaderDecode.Fmul_I); + Set("0101110001101x", ShaderDecode.Fmul_R); + Set("010010111011xx", ShaderDecode.Fsetp_C); + Set("0011011x1011xx", ShaderDecode.Fsetp_I); + Set("010110111011xx", ShaderDecode.Fsetp_R); + Set("0100110010111x", ShaderDecode.I2f_C); + Set("0011100x10111x", ShaderDecode.I2f_I); + Set("0101110010111x", ShaderDecode.I2f_R); + Set("11100000xxxxxx", ShaderDecode.Ipa); + Set("010010110110xx", ShaderDecode.Isetp_C); + Set("0011011x0110xx", ShaderDecode.Isetp_I); + Set("010110110110xx", ShaderDecode.Isetp_R); + Set("111000110011xx", ShaderDecode.Kil); + Set("1110111111011x", ShaderDecode.Ld_A); + Set("000001xxxxxxxx", ShaderDecode.Lop32i); + Set("0100110010011x", ShaderDecode.Mov_C); + Set("0011100x10011x", ShaderDecode.Mov_I); + Set("0101110010011x", ShaderDecode.Mov_R); + Set("000000010000xx", ShaderDecode.Mov32i); + Set("0101000010000x", ShaderDecode.Mufu); + Set("0100110000101x", ShaderDecode.Shr_C); + Set("0011100x00101x", ShaderDecode.Shr_I); + Set("0101110000101x", ShaderDecode.Shr_R); + Set("1110111111110x", ShaderDecode.St_A); + Set("1101111101001x", ShaderDecode.Texq); + Set("1101100xxxxxxx", ShaderDecode.Texs); + Set("1101101xxxxxxx", ShaderDecode.Tlds); +#endregion + } + + private static void Set(string Encoding, ShaderDecodeFunc Func) + { + if (Encoding.Length != EncodingBits) + { + throw new ArgumentException(nameof(Encoding)); + } + + int Bit = Encoding.Length - 1; + int Value = 0; + int XMask = 0; + int XBits = 0; + + int[] XPos = new int[Encoding.Length]; + + for (int Index = 0; Index < Encoding.Length; Index++, Bit--) + { + char Chr = Encoding[Index]; + + if (Chr == '1') + { + Value |= 1 << Bit; + } + else if (Chr == 'x') + { + XMask |= 1 << Bit; + + XPos[XBits++] = Bit; + } + } + + XMask = ~XMask; + + for (int Index = 0; Index < (1 << XBits); Index++) + { + Value &= XMask; + + for (int X = 0; X < XBits; X++) + { + Value |= ((Index >> X) & 1) << XPos[X]; + } + + OpCodes[Value] = Func; + } + } + + public static ShaderDecodeFunc GetDecoder(long OpCode) + { + return OpCodes[(ulong)OpCode >> (64 - EncodingBits)]; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderOper.cs b/Ryujinx.Graphics/Gal/Shader/ShaderOper.cs new file mode 100644 index 000000000..aa4854828 --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderOper.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Graphics.Gal.Shader +{ + enum ShaderOper + { + CR, + Imm, + Immf, + RC, + RR + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderTexqInfo.cs b/Ryujinx.Graphics/Gal/Shader/ShaderTexqInfo.cs new file mode 100644 index 000000000..9158662cc --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderTexqInfo.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Graphics.Gal.Shader +{ + enum ShaderTexqInfo + { + Dimension = 1, + TextureType = 2, + SamplePos = 5, + Filter = 16, + Lod = 18, + Wrap = 20, + BorderColor = 22 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/ShaderDeclInfo.cs b/Ryujinx.Graphics/Gal/ShaderDeclInfo.cs new file mode 100644 index 000000000..d400850c8 --- /dev/null +++ b/Ryujinx.Graphics/Gal/ShaderDeclInfo.cs @@ -0,0 +1,27 @@ +namespace Ryujinx.Graphics.Gal +{ + public class ShaderDeclInfo + { + public string Name { get; private set; } + + public int Index { get; private set; } + public int Cbuf { get; private set; } + public int Size { get; private set; } + + public ShaderDeclInfo(string Name, int Index, int Cbuf = 0, int Size = 1) + { + this.Name = Name; + this.Index = Index; + this.Cbuf = Cbuf; + this.Size = Size; + } + + internal void Enlarge(int NewSize) + { + if (Size < NewSize) + { + Size = NewSize; + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/ShaderException.cs b/Ryujinx.Graphics/Gal/ShaderException.cs new file mode 100644 index 000000000..9bc87ff3d --- /dev/null +++ b/Ryujinx.Graphics/Gal/ShaderException.cs @@ -0,0 +1,11 @@ +using System; + +namespace Ryujinx.Graphics.Gal +{ + class ShaderException : Exception + { + public ShaderException() : base() { } + + public ShaderException(string Message) : base(Message) { } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gpu/BCn.cs b/Ryujinx.Graphics/Gal/Texture/BCn.cs similarity index 85% rename from Ryujinx.Graphics/Gpu/BCn.cs rename to Ryujinx.Graphics/Gal/Texture/BCn.cs index b1caf4675..f23a86c2c 100644 --- a/Ryujinx.Graphics/Gpu/BCn.cs +++ b/Ryujinx.Graphics/Gal/Texture/BCn.cs @@ -1,14 +1,14 @@ using System; using System.Drawing; -namespace Ryujinx.Graphics.Gpu +namespace Ryujinx.Graphics.Gal.Texture { static class BCn { - public static byte[] DecodeBC1(NsGpuTexture Tex, int Offset) + public static byte[] DecodeBC1(GalTexture Texture, int Offset) { - int W = (Tex.Width + 3) / 4; - int H = (Tex.Height + 3) / 4; + int W = (Texture.Width + 3) / 4; + int H = (Texture.Height + 3) / 4; byte[] Output = new byte[W * H * 64]; @@ -20,7 +20,7 @@ namespace Ryujinx.Graphics.Gpu { int IOffs = Offset + Swizzle.GetSwizzledAddress64(X, Y) * 8; - byte[] Tile = BCnDecodeTile(Tex.Data, IOffs, true); + byte[] Tile = BCnDecodeTile(Texture.Data, IOffs, true); int TOffset = 0; @@ -44,10 +44,10 @@ namespace Ryujinx.Graphics.Gpu return Output; } - public static byte[] DecodeBC2(NsGpuTexture Tex, int Offset) + public static byte[] DecodeBC2(GalTexture Texture, int Offset) { - int W = (Tex.Width + 3) / 4; - int H = (Tex.Height + 3) / 4; + int W = (Texture.Width + 3) / 4; + int H = (Texture.Height + 3) / 4; byte[] Output = new byte[W * H * 64]; @@ -59,10 +59,10 @@ namespace Ryujinx.Graphics.Gpu { int IOffs = Offset + Swizzle.GetSwizzledAddress128(X, Y) * 16; - byte[] Tile = BCnDecodeTile(Tex.Data, IOffs + 8, false); + byte[] Tile = BCnDecodeTile(Texture.Data, IOffs + 8, false); - int AlphaLow = Get32(Tex.Data, IOffs + 0); - int AlphaHigh = Get32(Tex.Data, IOffs + 4); + int AlphaLow = Get32(Texture.Data, IOffs + 0); + int AlphaHigh = Get32(Texture.Data, IOffs + 4); ulong AlphaCh = (uint)AlphaLow | (ulong)AlphaHigh << 32; @@ -90,10 +90,10 @@ namespace Ryujinx.Graphics.Gpu return Output; } - public static byte[] DecodeBC3(NsGpuTexture Tex, int Offset) + public static byte[] DecodeBC3(GalTexture Texture, int Offset) { - int W = (Tex.Width + 3) / 4; - int H = (Tex.Height + 3) / 4; + int W = (Texture.Width + 3) / 4; + int H = (Texture.Height + 3) / 4; byte[] Output = new byte[W * H * 64]; @@ -105,17 +105,17 @@ namespace Ryujinx.Graphics.Gpu { int IOffs = Offset + Swizzle.GetSwizzledAddress128(X, Y) * 16; - byte[] Tile = BCnDecodeTile(Tex.Data, IOffs + 8, false); + byte[] Tile = BCnDecodeTile(Texture.Data, IOffs + 8, false); byte[] Alpha = new byte[8]; - Alpha[0] = Tex.Data[IOffs + 0]; - Alpha[1] = Tex.Data[IOffs + 1]; + Alpha[0] = Texture.Data[IOffs + 0]; + Alpha[1] = Texture.Data[IOffs + 1]; CalculateBC3Alpha(Alpha); - int AlphaLow = Get32(Tex.Data, IOffs + 2); - int AlphaHigh = Get16(Tex.Data, IOffs + 6); + int AlphaLow = Get32(Texture.Data, IOffs + 2); + int AlphaHigh = Get16(Texture.Data, IOffs + 6); ulong AlphaCh = (uint)AlphaLow | (ulong)AlphaHigh << 32; @@ -143,10 +143,10 @@ namespace Ryujinx.Graphics.Gpu return Output; } - public static byte[] DecodeBC4(NsGpuTexture Tex, int Offset) + public static byte[] DecodeBC4(GalTexture Texture, int Offset) { - int W = (Tex.Width + 3) / 4; - int H = (Tex.Height + 3) / 4; + int W = (Texture.Width + 3) / 4; + int H = (Texture.Height + 3) / 4; byte[] Output = new byte[W * H * 64]; @@ -160,13 +160,13 @@ namespace Ryujinx.Graphics.Gpu byte[] Red = new byte[8]; - Red[0] = Tex.Data[IOffs + 0]; - Red[1] = Tex.Data[IOffs + 1]; + Red[0] = Texture.Data[IOffs + 0]; + Red[1] = Texture.Data[IOffs + 1]; CalculateBC3Alpha(Red); - int RedLow = Get32(Tex.Data, IOffs + 2); - int RedHigh = Get16(Tex.Data, IOffs + 6); + int RedLow = Get32(Texture.Data, IOffs + 2); + int RedHigh = Get16(Texture.Data, IOffs + 6); ulong RedCh = (uint)RedLow | (ulong)RedHigh << 32; @@ -194,10 +194,10 @@ namespace Ryujinx.Graphics.Gpu return Output; } - public static byte[] DecodeBC5(NsGpuTexture Tex, int Offset, bool SNorm) + public static byte[] DecodeBC5(GalTexture Texture, int Offset, bool SNorm) { - int W = (Tex.Width + 3) / 4; - int H = (Tex.Height + 3) / 4; + int W = (Texture.Width + 3) / 4; + int H = (Texture.Height + 3) / 4; byte[] Output = new byte[W * H * 64]; @@ -212,11 +212,11 @@ namespace Ryujinx.Graphics.Gpu byte[] Red = new byte[8]; byte[] Green = new byte[8]; - Red[0] = Tex.Data[IOffs + 0]; - Red[1] = Tex.Data[IOffs + 1]; + Red[0] = Texture.Data[IOffs + 0]; + Red[1] = Texture.Data[IOffs + 1]; - Green[0] = Tex.Data[IOffs + 8]; - Green[1] = Tex.Data[IOffs + 9]; + Green[0] = Texture.Data[IOffs + 8]; + Green[1] = Texture.Data[IOffs + 9]; if (SNorm) { @@ -229,11 +229,11 @@ namespace Ryujinx.Graphics.Gpu CalculateBC3Alpha(Green); } - int RedLow = Get32(Tex.Data, IOffs + 2); - int RedHigh = Get16(Tex.Data, IOffs + 6); + int RedLow = Get32(Texture.Data, IOffs + 2); + int RedHigh = Get16(Texture.Data, IOffs + 6); - int GreenLow = Get32(Tex.Data, IOffs + 10); - int GreenHigh = Get16(Tex.Data, IOffs + 14); + int GreenLow = Get32(Texture.Data, IOffs + 10); + int GreenHigh = Get16(Texture.Data, IOffs + 14); ulong RedCh = (uint)RedLow | (ulong)RedHigh << 32; ulong GreenCh = (uint)GreenLow | (ulong)GreenHigh << 32; diff --git a/Ryujinx.Graphics/Gpu/SwizzleAddr.cs b/Ryujinx.Graphics/Gal/Texture/SwizzleAddr.cs similarity index 98% rename from Ryujinx.Graphics/Gpu/SwizzleAddr.cs rename to Ryujinx.Graphics/Gal/Texture/SwizzleAddr.cs index 08e61eb58..b67b841bc 100644 --- a/Ryujinx.Graphics/Gpu/SwizzleAddr.cs +++ b/Ryujinx.Graphics/Gal/Texture/SwizzleAddr.cs @@ -1,6 +1,6 @@ using System; -namespace Ryujinx.Graphics.Gpu +namespace Ryujinx.Graphics.Gal.Texture { class SwizzleAddr { @@ -109,7 +109,7 @@ namespace Ryujinx.Graphics.Gpu * y x x x x x x y y y y x y y x y 0 0 0 0 1024 x 1024 dxt5 * y y x x x x x x y y y y x y y x y x 0 0 0 2048 x 2048 dxt1 * y y y x x x x x x y y y y x y y x y x x 0 0 1024 x 1024 rgba8888 - * + * * Read from right to left, LSB first. */ int XCnt = XBase; diff --git a/Ryujinx.Graphics/Gal/Texture/TextureDecoder.cs b/Ryujinx.Graphics/Gal/Texture/TextureDecoder.cs new file mode 100644 index 000000000..4e50db51d --- /dev/null +++ b/Ryujinx.Graphics/Gal/Texture/TextureDecoder.cs @@ -0,0 +1,19 @@ +using System; + +namespace Ryujinx.Graphics.Gal.Texture +{ + static class TextureDecoder + { + public static byte[] Decode(GalTexture Texture) + { + switch (Texture.Format) + { + case GalTextureFormat.BC1: return BCn.DecodeBC1(Texture, 0); + case GalTextureFormat.BC2: return BCn.DecodeBC2(Texture, 0); + case GalTextureFormat.BC3: return BCn.DecodeBC3(Texture, 0); + } + + throw new NotImplementedException(Texture.Format.ToString()); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gpu/BlockLinearSwizzle.cs b/Ryujinx.Graphics/Gpu/BlockLinearSwizzle.cs new file mode 100644 index 000000000..d2cbb1443 --- /dev/null +++ b/Ryujinx.Graphics/Gpu/BlockLinearSwizzle.cs @@ -0,0 +1,57 @@ +namespace Ryujinx.Graphics.Gpu +{ + class BlockLinearSwizzle : ISwizzle + { + private int BhShift; + private int BppShift; + private int BhMask; + + private int XShift; + private int GobStride; + + public BlockLinearSwizzle(int Width, int Bpp, int BlockHeight = 16) + { + BhMask = (BlockHeight * 8) - 1; + + BhShift = CountLsbZeros(BlockHeight * 8); + BppShift = CountLsbZeros(Bpp); + + int WidthInGobs = Width * Bpp / 64; + + GobStride = 512 * BlockHeight * WidthInGobs; + + XShift = CountLsbZeros(512 * BlockHeight); + } + + private int CountLsbZeros(int Value) + { + int Count = 0; + + while (((Value >> Count) & 1) == 0) + { + Count++; + } + + return Count; + } + + public int GetSwizzleOffset(int X, int Y) + { + X <<= BppShift; + + int Position = (Y >> BhShift) * GobStride; + + Position += (X >> 6) << XShift; + + Position += ((Y & BhMask) >> 3) << 9; + + Position += ((X & 0x3f) >> 5) << 8; + Position += ((Y & 0x07) >> 1) << 6; + Position += ((X & 0x1f) >> 4) << 5; + Position += ((Y & 0x01) >> 0) << 4; + Position += ((X & 0x0f) >> 0) << 0; + + return Position; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gpu/INvGpuEngine.cs b/Ryujinx.Graphics/Gpu/INvGpuEngine.cs new file mode 100644 index 000000000..17e9b435c --- /dev/null +++ b/Ryujinx.Graphics/Gpu/INvGpuEngine.cs @@ -0,0 +1,11 @@ +using ChocolArm64.Memory; + +namespace Ryujinx.Graphics.Gpu +{ + interface INvGpuEngine + { + int[] Registers { get; } + + void CallMethod(AMemory Memory, NsGpuPBEntry PBEntry); + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gpu/ISwizzle.cs b/Ryujinx.Graphics/Gpu/ISwizzle.cs new file mode 100644 index 000000000..755051d0c --- /dev/null +++ b/Ryujinx.Graphics/Gpu/ISwizzle.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.Graphics.Gpu +{ + interface ISwizzle + { + int GetSwizzleOffset(int X, int Y); + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gpu/LinearSwizzle.cs b/Ryujinx.Graphics/Gpu/LinearSwizzle.cs new file mode 100644 index 000000000..c7a6b304e --- /dev/null +++ b/Ryujinx.Graphics/Gpu/LinearSwizzle.cs @@ -0,0 +1,19 @@ +namespace Ryujinx.Graphics.Gpu +{ + class LinearSwizzle : ISwizzle + { + private int Pitch; + private int Bpp; + + public LinearSwizzle(int Pitch, int Bpp) + { + this.Pitch = Pitch; + this.Bpp = Bpp; + } + + public int GetSwizzleOffset(int X, int Y) + { + return X * Bpp + Y * Pitch; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gpu/MacroInterpreter.cs b/Ryujinx.Graphics/Gpu/MacroInterpreter.cs new file mode 100644 index 000000000..233baac8b --- /dev/null +++ b/Ryujinx.Graphics/Gpu/MacroInterpreter.cs @@ -0,0 +1,420 @@ +using ChocolArm64.Memory; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Gpu +{ + class MacroInterpreter + { + private enum AssignmentOperation + { + IgnoreAndFetch = 0, + Move = 1, + MoveAndSetMaddr = 2, + FetchAndSend = 3, + MoveAndSend = 4, + FetchAndSetMaddr = 5, + MoveAndSetMaddrThenFetchAndSend = 6, + MoveAndSetMaddrThenSendHigh = 7 + } + + private enum AluOperation + { + AluReg = 0, + AddImmediate = 1, + BitfieldReplace = 2, + BitfieldExtractLslImm = 3, + BitfieldExtractLslReg = 4, + ReadImmediate = 5 + } + + private enum AluRegOperation + { + Add = 0, + AddWithCarry = 1, + Subtract = 2, + SubtractWithBorrow = 3, + BitwiseExclusiveOr = 8, + BitwiseOr = 9, + BitwiseAnd = 10, + BitwiseAndNot = 11, + BitwiseNotAnd = 12 + } + + private NvGpuFifo PFifo; + private INvGpuEngine Engine; + + public Queue Fifo { get; private set; } + + private int[] Gprs; + + private int MethAddr; + private int MethIncr; + + private bool Carry; + + private int OpCode; + + private int PipeOp; + + private long Pc; + + public MacroInterpreter(NvGpuFifo PFifo, INvGpuEngine Engine) + { + this.PFifo = PFifo; + this.Engine = Engine; + + Fifo = new Queue(); + + Gprs = new int[8]; + } + + public void Execute(AMemory Memory, long Position, int Param) + { + Reset(); + + Gprs[1] = Param; + + Pc = Position; + + FetchOpCode(Memory); + + while (Step(Memory)); + + //Due to the delay slot, we still need to execute + //one more instruction before we actually exit. + Step(Memory); + } + + private void Reset() + { + for (int Index = 0; Index < Gprs.Length; Index++) + { + Gprs[Index] = 0; + } + + MethAddr = 0; + MethIncr = 0; + + Carry = false; + } + + private bool Step(AMemory Memory) + { + long BaseAddr = Pc - 4; + + FetchOpCode(Memory); + + if ((OpCode & 7) < 7) + { + //Operation produces a value. + AssignmentOperation AsgOp = (AssignmentOperation)((OpCode >> 4) & 7); + + int Result = GetAluResult(); + + switch (AsgOp) + { + //Fetch parameter and ignore result. + case AssignmentOperation.IgnoreAndFetch: + { + SetDstGpr(FetchParam()); + + break; + } + + //Move result. + case AssignmentOperation.Move: + { + SetDstGpr(Result); + + break; + } + + //Move result and use as Method Address. + case AssignmentOperation.MoveAndSetMaddr: + { + SetDstGpr(Result); + + SetMethAddr(Result); + + break; + } + + //Fetch parameter and send result. + case AssignmentOperation.FetchAndSend: + { + SetDstGpr(FetchParam()); + + Send(Memory, Result); + + break; + } + + //Move and send result. + case AssignmentOperation.MoveAndSend: + { + SetDstGpr(Result); + + Send(Memory, Result); + + break; + } + + //Fetch parameter and use result as Method Address. + case AssignmentOperation.FetchAndSetMaddr: + { + SetDstGpr(FetchParam()); + + SetMethAddr(Result); + + break; + } + + //Move result and use as Method Address, then fetch and send paramter. + case AssignmentOperation.MoveAndSetMaddrThenFetchAndSend: + { + SetDstGpr(Result); + + SetMethAddr(Result); + + Send(Memory, FetchParam()); + + break; + } + + //Move result and use as Method Address, then send bits 17:12 of result. + case AssignmentOperation.MoveAndSetMaddrThenSendHigh: + { + SetDstGpr(Result); + + SetMethAddr(Result); + + Send(Memory, (Result >> 12) & 0x3f); + + break; + } + } + } + else + { + //Branch. + bool OnNotZero = ((OpCode >> 4) & 1) != 0; + + bool Taken = OnNotZero + ? GetGprA() != 0 + : GetGprA() == 0; + + if (Taken) + { + Pc = BaseAddr + (GetImm() << 2); + + bool NoDelays = (OpCode & 0x20) != 0; + + if (NoDelays) + { + FetchOpCode(Memory); + } + + return true; + } + } + + bool Exit = (OpCode & 0x80) != 0; + + return !Exit; + } + + private void FetchOpCode(AMemory Memory) + { + OpCode = PipeOp; + + PipeOp = Memory.ReadInt32(Pc); + + Pc += 4; + } + + private int GetAluResult() + { + AluOperation Op = (AluOperation)(OpCode & 7); + + switch (Op) + { + case AluOperation.AluReg: + { + AluRegOperation AluOp = (AluRegOperation)((OpCode >> 17) & 0x1f); + + return GetAluResult(AluOp, GetGprA(), GetGprB()); + } + + case AluOperation.AddImmediate: + { + return GetGprA() + GetImm(); + } + + case AluOperation.BitfieldReplace: + case AluOperation.BitfieldExtractLslImm: + case AluOperation.BitfieldExtractLslReg: + { + int BfSrcBit = (OpCode >> 17) & 0x1f; + int BfSize = (OpCode >> 22) & 0x1f; + int BfDstBit = (OpCode >> 27) & 0x1f; + + int BfMask = (1 << BfSize) - 1; + + int Dst = GetGprA(); + int Src = GetGprB(); + + switch (Op) + { + case AluOperation.BitfieldReplace: + { + Src = (int)((uint)Src >> BfSrcBit) & BfMask; + + Dst &= ~(BfMask << BfDstBit); + + Dst |= Src << BfDstBit; + + return Dst; + } + + case AluOperation.BitfieldExtractLslImm: + { + Src = (int)((uint)Src >> Dst) & BfMask; + + return Src << BfDstBit; + } + + case AluOperation.BitfieldExtractLslReg: + { + Src = (int)((uint)Src >> BfSrcBit) & BfMask; + + return Src << Dst; + } + } + + break; + } + + case AluOperation.ReadImmediate: + { + return Read(GetGprA() + GetImm()); + } + } + + throw new ArgumentException(nameof(OpCode)); + } + + private int GetAluResult(AluRegOperation AluOp, int A, int B) + { + switch (AluOp) + { + case AluRegOperation.Add: + { + ulong Result = (ulong)A + (ulong)B; + + Carry = Result > 0xffffffff; + + return (int)Result; + } + + case AluRegOperation.AddWithCarry: + { + ulong Result = (ulong)A + (ulong)B + (Carry ? 1UL : 0UL); + + Carry = Result > 0xffffffff; + + return (int)Result; + } + + case AluRegOperation.Subtract: + { + ulong Result = (ulong)A - (ulong)B; + + Carry = Result < 0x100000000; + + return (int)Result; + } + + case AluRegOperation.SubtractWithBorrow: + { + ulong Result = (ulong)A - (ulong)B - (Carry ? 0UL : 1UL); + + Carry = Result < 0x100000000; + + return (int)Result; + } + + case AluRegOperation.BitwiseExclusiveOr: return A ^ B; + case AluRegOperation.BitwiseOr: return A | B; + case AluRegOperation.BitwiseAnd: return A & B; + case AluRegOperation.BitwiseAndNot: return A & ~B; + case AluRegOperation.BitwiseNotAnd: return ~(A & B); + } + + throw new ArgumentOutOfRangeException(nameof(AluOp)); + } + + private int GetImm() + { + //Note: The immediate is signed, the sign-extension is intended here. + return OpCode >> 14; + } + + private void SetMethAddr(int Value) + { + MethAddr = (Value >> 0) & 0xfff; + MethIncr = (Value >> 12) & 0x3f; + } + + private void SetDstGpr(int Value) + { + Gprs[(OpCode >> 8) & 7] = Value; + } + + private int GetGprA() + { + return GetGprValue((OpCode >> 11) & 7); + } + + private int GetGprB() + { + return GetGprValue((OpCode >> 14) & 7); + } + + private int GetGprValue(int Index) + { + return Index != 0 ? Gprs[Index] : 0; + } + + private int FetchParam() + { + int Value; + + //If we don't have any parameters in the FIFO, + //keep running the PFIFO engine until it writes the parameters. + while (!Fifo.TryDequeue(out Value)) + { + if (!PFifo.Step()) + { + return 0; + } + } + + return Value; + } + + private int Read(int Reg) + { + return Engine.Registers[Reg]; + } + + private void Send(AMemory Memory, int Value) + { + NsGpuPBEntry PBEntry = new NsGpuPBEntry(MethAddr, 0, Value); + + Engine.CallMethod(Memory, PBEntry); + + MethAddr += MethIncr; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gpu/NsGpu.cs b/Ryujinx.Graphics/Gpu/NsGpu.cs index 133d0af25..9a2e90128 100644 --- a/Ryujinx.Graphics/Gpu/NsGpu.cs +++ b/Ryujinx.Graphics/Gpu/NsGpu.cs @@ -1,5 +1,5 @@ -using ChocolArm64.Memory; using Ryujinx.Graphics.Gal; +using System.Threading; namespace Ryujinx.Graphics.Gpu { @@ -9,7 +9,13 @@ namespace Ryujinx.Graphics.Gpu internal NsGpuMemoryMgr MemoryMgr { get; private set; } - internal NsGpuPGraph PGraph { get; private set; } + public NvGpuFifo Fifo { get; private set; } + + public NvGpuEngine3d Engine3d { get; private set; } + + private Thread FifoProcessing; + + private bool KeepRunning; public NsGpu(IGalRenderer Renderer) { @@ -17,7 +23,15 @@ namespace Ryujinx.Graphics.Gpu MemoryMgr = new NsGpuMemoryMgr(); - PGraph = new NsGpuPGraph(this); + Fifo = new NvGpuFifo(this); + + Engine3d = new NvGpuEngine3d(this); + + KeepRunning = true; + + FifoProcessing = new Thread(ProcessFifo); + + FifoProcessing.Start(); } public long GetCpuAddr(long Position) @@ -35,11 +49,6 @@ namespace Ryujinx.Graphics.Gpu return MemoryMgr.Map(CpuAddr, GpuAddr, Size); } - public void ProcessPushBuffer(NsGpuPBEntry[] PushBuffer, AMemory Memory) - { - PGraph.ProcessPushBuffer(PushBuffer, Memory); - } - public long ReserveMemory(long Size, long Align) { return MemoryMgr.Reserve(Size, Align); @@ -49,5 +58,15 @@ namespace Ryujinx.Graphics.Gpu { return MemoryMgr.Reserve(GpuAddr, Size, Align); } + + private void ProcessFifo() + { + while (KeepRunning) + { + Fifo.DispatchCalls(); + + Thread.Yield(); + } + } } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Gpu/NsGpuPBEntry.cs b/Ryujinx.Graphics/Gpu/NsGpuPBEntry.cs index 8063651aa..d405a93c6 100644 --- a/Ryujinx.Graphics/Gpu/NsGpuPBEntry.cs +++ b/Ryujinx.Graphics/Gpu/NsGpuPBEntry.cs @@ -1,13 +1,11 @@ using System; -using System.Collections.Generic; using System.Collections.ObjectModel; -using System.IO; namespace Ryujinx.Graphics.Gpu { public struct NsGpuPBEntry { - public NsGpuRegister Register { get; private set; } + public int Method { get; private set; } public int SubChannel { get; private set; } @@ -15,65 +13,11 @@ namespace Ryujinx.Graphics.Gpu public ReadOnlyCollection Arguments => Array.AsReadOnly(m_Arguments); - public NsGpuPBEntry(NsGpuRegister Register, int SubChannel, params int[] Arguments) + public NsGpuPBEntry(int Method, int SubChannel, params int[] Arguments) { - this.Register = Register; + this.Method = Method; this.SubChannel = SubChannel; this.m_Arguments = Arguments; } - - public static NsGpuPBEntry[] DecodePushBuffer(byte[] Data) - { - using (MemoryStream MS = new MemoryStream(Data)) - { - BinaryReader Reader = new BinaryReader(MS); - - List GpFifos = new List(); - - bool CanRead() => MS.Position + 4 <= MS.Length; - - while (CanRead()) - { - int Packed = Reader.ReadInt32(); - - int Reg = (Packed << 2) & 0x7ffc; - int SubC = (Packed >> 13) & 7; - int Args = (Packed >> 16) & 0x1fff; - int Mode = (Packed >> 29) & 7; - - if (Mode == 4) - { - //Inline Mode. - GpFifos.Add(new NsGpuPBEntry((NsGpuRegister)Reg, SubC, Args)); - } - else - { - //Word mode. - if (Mode == 1) - { - //Sequential Mode. - for (int Index = 0; Index < Args && CanRead(); Index++, Reg += 4) - { - GpFifos.Add(new NsGpuPBEntry((NsGpuRegister)Reg, SubC, Reader.ReadInt32())); - } - } - else - { - //Non-Sequential Mode. - int[] Arguments = new int[Args]; - - for (int Index = 0; Index < Args && CanRead(); Index++) - { - Arguments[Index] = Reader.ReadInt32(); - } - - GpFifos.Add(new NsGpuPBEntry((NsGpuRegister)Reg, SubC, Arguments)); - } - } - } - - return GpFifos.ToArray(); - } - } } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Gpu/NsGpuPGraph.cs b/Ryujinx.Graphics/Gpu/NsGpuPGraph.cs deleted file mode 100644 index eb893f74c..000000000 --- a/Ryujinx.Graphics/Gpu/NsGpuPGraph.cs +++ /dev/null @@ -1,276 +0,0 @@ -using ChocolArm64.Memory; -using Ryujinx.Graphics.Gal; -using System.Collections.Generic; - -namespace Ryujinx.Graphics.Gpu -{ - class NsGpuPGraph - { - private NsGpu Gpu; - - private int[] Registers; - - public NsGpuEngine[] SubChannels; - - private Dictionary CurrentVertexBuffers; - - public NsGpuPGraph(NsGpu Gpu) - { - this.Gpu = Gpu; - - Registers = new int[0x1000]; - - SubChannels = new NsGpuEngine[8]; - - CurrentVertexBuffers = new Dictionary(); - } - - public void ProcessPushBuffer(NsGpuPBEntry[] PushBuffer, AMemory Memory) - { - bool HasQuery = false; - - foreach (NsGpuPBEntry Entry in PushBuffer) - { - if (Entry.Arguments.Count == 1) - { - SetRegister(Entry.Register, Entry.Arguments[0]); - } - - switch (Entry.Register) - { - case NsGpuRegister.BindChannel: - if (Entry.Arguments.Count > 0) - { - SubChannels[Entry.SubChannel] = (NsGpuEngine)Entry.Arguments[0]; - } - break; - - case NsGpuRegister._3dVertexArray0Fetch: - SendVertexBuffers(Memory); - break; - - case NsGpuRegister._3dCbData0: - if (GetRegister(NsGpuRegister._3dCbPos) == 0x20) - { - SendTexture(Memory); - } - break; - - case NsGpuRegister._3dQueryAddressHigh: - case NsGpuRegister._3dQueryAddressLow: - case NsGpuRegister._3dQuerySequence: - case NsGpuRegister._3dQueryGet: - HasQuery = true; - break; - } - } - - if (HasQuery) - { - long Position = - (long)GetRegister(NsGpuRegister._3dQueryAddressHigh) << 32 | - (long)GetRegister(NsGpuRegister._3dQueryAddressLow) << 0; - - int Seq = GetRegister(NsGpuRegister._3dQuerySequence); - int Get = GetRegister(NsGpuRegister._3dQueryGet); - - int Mode = Get & 3; - - if (Mode == 0) - { - //Write - Position = Gpu.MemoryMgr.GetCpuAddr(Position); - - if (Position != -1) - { - Gpu.Renderer.QueueAction(delegate() - { - Memory.WriteInt32(Position, Seq); - }); - } - } - } - } - - private void SendVertexBuffers(AMemory Memory) - { - long Position = - (long)GetRegister(NsGpuRegister._3dVertexArray0StartHigh) << 32 | - (long)GetRegister(NsGpuRegister._3dVertexArray0StartLow) << 0; - - long Limit = - (long)GetRegister(NsGpuRegister._3dVertexArray0LimitHigh) << 32 | - (long)GetRegister(NsGpuRegister._3dVertexArray0LimitLow) << 0; - - int VbIndex = CurrentVertexBuffers.Count; - - if (!CurrentVertexBuffers.TryAdd(Position, VbIndex)) - { - VbIndex = CurrentVertexBuffers[Position]; - } - - if (Limit != 0) - { - long Size = (Limit - Position) + 1; - - Position = Gpu.MemoryMgr.GetCpuAddr(Position); - - if (Position != -1) - { - byte[] Buffer = AMemoryHelper.ReadBytes(Memory, Position, (int)Size); - - int Stride = GetRegister(NsGpuRegister._3dVertexArray0Fetch) & 0xfff; - - List Attribs = new List(); - - for (int Attr = 0; Attr < 16; Attr++) - { - int Packed = GetRegister(NsGpuRegister._3dVertexAttrib0Format + Attr * 4); - - GalVertexAttrib Attrib = new GalVertexAttrib(Attr, - (Packed >> 0) & 0x1f, - ((Packed >> 6) & 0x1) != 0, - (Packed >> 7) & 0x3fff, - (GalVertexAttribSize)((Packed >> 21) & 0x3f), - (GalVertexAttribType)((Packed >> 27) & 0x7), - ((Packed >> 31) & 0x1) != 0); - - if (Attrib.Offset < Stride) - { - Attribs.Add(Attrib); - } - } - - Gpu.Renderer.QueueAction(delegate() - { - Gpu.Renderer.SendVertexBuffer(VbIndex, Buffer, Stride, Attribs.ToArray()); - }); - } - } - } - - private void SendTexture(AMemory Memory) - { - long TicPos = (long)GetRegister(NsGpuRegister._3dTicAddressHigh) << 32 | - (long)GetRegister(NsGpuRegister._3dTicAddressLow) << 0; - - int CbData = GetRegister(NsGpuRegister._3dCbData0); - - int TicIndex = (CbData >> 0) & 0xfffff; - int TscIndex = (CbData >> 20) & 0xfff; //I guess? - - TicPos = Gpu.MemoryMgr.GetCpuAddr(TicPos + TicIndex * 0x20); - - if (TicPos != -1) - { - int Word0 = Memory.ReadInt32(TicPos + 0x0); - int Word1 = Memory.ReadInt32(TicPos + 0x4); - int Word2 = Memory.ReadInt32(TicPos + 0x8); - int Word3 = Memory.ReadInt32(TicPos + 0xc); - int Word4 = Memory.ReadInt32(TicPos + 0x10); - int Word5 = Memory.ReadInt32(TicPos + 0x14); - int Word6 = Memory.ReadInt32(TicPos + 0x18); - int Word7 = Memory.ReadInt32(TicPos + 0x1c); - - long TexAddress = Word1; - - TexAddress |= (long)(Word2 & 0xff) << 32; - - TexAddress = Gpu.MemoryMgr.GetCpuAddr(TexAddress); - - if (TexAddress != -1) - { - NsGpuTextureFormat Format = (NsGpuTextureFormat)(Word0 & 0x7f); - - int Width = (Word4 & 0xffff) + 1; - int Height = (Word5 & 0xffff) + 1; - - byte[] Buffer = GetDecodedTexture(Memory, Format, TexAddress, Width, Height); - - if (Buffer != null) - { - Gpu.Renderer.QueueAction(delegate() - { - Gpu.Renderer.SendR8G8B8A8Texture(0, Buffer, Width, Height); - }); - } - } - } - } - - private static byte[] GetDecodedTexture( - AMemory Memory, - NsGpuTextureFormat Format, - long Position, - int Width, - int Height) - { - byte[] Data = null; - - switch (Format) - { - case NsGpuTextureFormat.BC1: - { - int Size = (Width * Height) >> 1; - - Data = AMemoryHelper.ReadBytes(Memory, Position, Size); - - Data = BCn.DecodeBC1(new NsGpuTexture() - { - Width = Width, - Height = Height, - Data = Data - }, 0); - - break; - } - - case NsGpuTextureFormat.BC2: - { - int Size = Width * Height; - - Data = AMemoryHelper.ReadBytes(Memory, Position, Size); - - Data = BCn.DecodeBC2(new NsGpuTexture() - { - Width = Width, - Height = Height, - Data = Data - }, 0); - - break; - } - - case NsGpuTextureFormat.BC3: - { - int Size = Width * Height; - - Data = AMemoryHelper.ReadBytes(Memory, Position, Size); - - Data = BCn.DecodeBC3(new NsGpuTexture() - { - Width = Width, - Height = Height, - Data = Data - }, 0); - - break; - } - - //default: throw new NotImplementedException(Format.ToString()); - } - - return Data; - } - - public int GetRegister(NsGpuRegister Register) - { - return Registers[((int)Register >> 2) & 0xfff]; - } - - public void SetRegister(NsGpuRegister Register, int Value) - { - Registers[((int)Register >> 2) & 0xfff] = Value; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gpu/NsGpuRegister.cs b/Ryujinx.Graphics/Gpu/NsGpuRegister.cs deleted file mode 100644 index 319e2c01f..000000000 --- a/Ryujinx.Graphics/Gpu/NsGpuRegister.cs +++ /dev/null @@ -1,93 +0,0 @@ -namespace Ryujinx.Graphics.Gpu -{ - public enum NsGpuRegister - { - BindChannel = 0, - - _2dClipEnable = 0x0290, - _2dOperation = 0x02ac, - - _3dGlobalBase = 0x02c8, - _3dRt0AddressHigh = 0x0800, - _3dRt0AddressLow = 0x0804, - _3dRt0Horiz = 0x0808, - _3dRt0Vert = 0x080c, - _3dRt0Format = 0x0810, - _3dRt0BlockDimensions = 0x0814, - _3dRt0ArrayMode = 0x0818, - _3dRt0LayerStride = 0x081c, - _3dRt0BaseLayer = 0x0820, - _3dViewportScaleX = 0x0a00, - _3dViewportScaleY = 0x0a04, - _3dViewportScaleZ = 0x0a08, - _3dViewportTranslateX = 0x0a0c, - _3dViewportTranslateY = 0x0a10, - _3dViewportTranslateZ = 0x0a14, - _3dViewportHoriz = 0x0c00, - _3dViewportVert = 0x0c04, - _3dDepthRangeNear = 0x0c08, - _3dDepthRangeFar = 0x0c0c, - _3dClearColorR = 0x0d80, - _3dClearColorG = 0x0d84, - _3dClearColorB = 0x0d88, - _3dClearColorA = 0x0d8c, - _3dScreenScissorHoriz = 0x0ff4, - _3dScreenScissorVert = 0x0ff8, - _3dVertexAttrib0Format = 0x1160, - _3dVertexAttrib1Format = 0x1164, - _3dVertexAttrib2Format = 0x1168, - _3dVertexAttrib3Format = 0x116c, - _3dVertexAttrib4Format = 0x1170, - _3dVertexAttrib5Format = 0x1174, - _3dVertexAttrib6Format = 0x1178, - _3dVertexAttrib7Format = 0x117c, - _3dVertexAttrib8Format = 0x1180, - _3dVertexAttrib9Format = 0x1184, - _3dVertexAttrib10Format = 0x1188, - _3dVertexAttrib11Format = 0x118c, - _3dVertexAttrib12Format = 0x1190, - _3dVertexAttrib13Format = 0x1194, - _3dVertexAttrib14Format = 0x1198, - _3dVertexAttrib15Format = 0x119c, - _3dScreenYControl = 0x13ac, - _3dTscAddressHigh = 0x155c, - _3dTscAddressLow = 0x1560, - _3dTscLimit = 0x1564, - _3dTicAddressHigh = 0x1574, - _3dTicAddressLow = 0x1578, - _3dTicLimit = 0x157c, - _3dMultiSampleMode = 0x15d0, - _3dVertexEndGl = 0x1614, - _3dVertexBeginGl = 0x1618, - _3dQueryAddressHigh = 0x1b00, - _3dQueryAddressLow = 0x1b04, - _3dQuerySequence = 0x1b08, - _3dQueryGet = 0x1b0c, - _3dVertexArray0Fetch = 0x1c00, - _3dVertexArray0StartHigh = 0x1c04, - _3dVertexArray0StartLow = 0x1c08, - _3dVertexArray1Fetch = 0x1c10, //todo: the rest - _3dVertexArray0LimitHigh = 0x1f00, - _3dVertexArray0LimitLow = 0x1f04, - _3dCbSize = 0x2380, - _3dCbAddressHigh = 0x2384, - _3dCbAddressLow = 0x2388, - _3dCbPos = 0x238c, - _3dCbData0 = 0x2390, - _3dCbData1 = 0x2394, - _3dCbData2 = 0x2398, - _3dCbData3 = 0x239c, - _3dCbData4 = 0x23a0, - _3dCbData5 = 0x23a4, - _3dCbData6 = 0x23a8, - _3dCbData7 = 0x23ac, - _3dCbData8 = 0x23b0, - _3dCbData9 = 0x23b4, - _3dCbData10 = 0x23b8, - _3dCbData11 = 0x23bc, - _3dCbData12 = 0x23c0, - _3dCbData13 = 0x23c4, - _3dCbData14 = 0x23c8, - _3dCbData15 = 0x23cc, - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gpu/NsGpuTexture.cs b/Ryujinx.Graphics/Gpu/NsGpuTexture.cs deleted file mode 100644 index aac422005..000000000 --- a/Ryujinx.Graphics/Gpu/NsGpuTexture.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Ryujinx.Graphics.Gpu -{ - struct NsGpuTexture - { - public int Width; - public int Height; - - public byte[] Data; - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gpu/NsGpuTextureFormat.cs b/Ryujinx.Graphics/Gpu/NsGpuTextureFormat.cs deleted file mode 100644 index 2993840be..000000000 --- a/Ryujinx.Graphics/Gpu/NsGpuTextureFormat.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Ryujinx.Graphics.Gpu -{ - enum NsGpuTextureFormat - { - BC1 = 0x24, - BC2 = 0x25, - BC3 = 0x26 - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gpu/NsGpuEngine.cs b/Ryujinx.Graphics/Gpu/NvGpuEngine.cs similarity index 61% rename from Ryujinx.Graphics/Gpu/NsGpuEngine.cs rename to Ryujinx.Graphics/Gpu/NvGpuEngine.cs index 118e2b72a..624915d0d 100644 --- a/Ryujinx.Graphics/Gpu/NsGpuEngine.cs +++ b/Ryujinx.Graphics/Gpu/NvGpuEngine.cs @@ -1,13 +1,11 @@ namespace Ryujinx.Graphics.Gpu { - enum NsGpuEngine + enum NvGpuEngine { - None = 0, _2d = 0x902d, _3d = 0xb197, Compute = 0xb1c0, Kepler = 0xa140, - Dma = 0xb0b5, - GpFifo = 0xb06f + Dma = 0xb0b5 } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Gpu/NvGpuEngine3d.cs b/Ryujinx.Graphics/Gpu/NvGpuEngine3d.cs new file mode 100644 index 000000000..88ad76334 --- /dev/null +++ b/Ryujinx.Graphics/Gpu/NvGpuEngine3d.cs @@ -0,0 +1,505 @@ +using ChocolArm64.Memory; +using Ryujinx.Graphics.Gal; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Gpu +{ + public class NvGpuEngine3d : INvGpuEngine + { + public int[] Registers { get; private set; } + + private NsGpu Gpu; + + private Dictionary Methods; + + private struct ConstBuffer + { + public bool Enabled; + public long Position; + public int Size; + } + + private ConstBuffer[] ConstBuffers; + + private HashSet FrameBuffers; + + public NvGpuEngine3d(NsGpu Gpu) + { + this.Gpu = Gpu; + + Registers = new int[0xe00]; + + Methods = new Dictionary(); + + void AddMethod(int Meth, int Count, int Stride, NvGpuMethod Method) + { + while (Count-- > 0) + { + Methods.Add(Meth, Method); + + Meth += Stride; + } + } + + AddMethod(0x585, 1, 1, VertexEndGl); + AddMethod(0x674, 1, 1, ClearBuffers); + AddMethod(0x6c3, 1, 1, QueryControl); + AddMethod(0x8e4, 16, 1, CbData); + AddMethod(0x904, 1, 1, CbBind); + + ConstBuffers = new ConstBuffer[18]; + + FrameBuffers = new HashSet(); + } + + public void CallMethod(AMemory Memory, NsGpuPBEntry PBEntry) + { + if (Methods.TryGetValue(PBEntry.Method, out NvGpuMethod Method)) + { + Method(Memory, PBEntry); + } + else + { + WriteRegister(PBEntry); + } + } + + private void VertexEndGl(AMemory Memory, NsGpuPBEntry PBEntry) + { + SetFrameBuffer(0); + + long[] Tags = UploadShaders(Memory); + + Gpu.Renderer.BindProgram(); + + SetAlphaBlending(); + + UploadTextures(Memory, Tags); + UploadUniforms(Memory); + UploadVertexArrays(Memory); + } + + private void ClearBuffers(AMemory Memory, NsGpuPBEntry PBEntry) + { + int Arg0 = PBEntry.Arguments[0]; + + int FbIndex = (Arg0 >> 6) & 0xf; + + int Layer = (Arg0 >> 10) & 0x3ff; + + GalClearBufferFlags Flags = (GalClearBufferFlags)(Arg0 & 0x3f); + + SetFrameBuffer(0); + + //TODO: Enable this once the frame buffer problems are fixed. + //Gpu.Renderer.ClearBuffers(Layer, Flags); + } + + private void SetFrameBuffer(int FbIndex) + { + long Address = MakeInt64From2xInt32(NvGpuEngine3dReg.FrameBufferNAddress + FbIndex * 0x10); + + FrameBuffers.Add(Address); + + int Width = ReadRegister(NvGpuEngine3dReg.FrameBufferNWidth + FbIndex * 0x10); + int Height = ReadRegister(NvGpuEngine3dReg.FrameBufferNHeight + FbIndex * 0x10); + + //Note: Using the Width/Height results seems to give incorrect results. + //Maybe the size of all frame buffers is hardcoded to screen size? This seems unlikely. + Gpu.Renderer.CreateFrameBuffer(Address, 1280, 720); + Gpu.Renderer.BindFrameBuffer(Address); + } + + private long[] UploadShaders(AMemory Memory) + { + long[] Tags = new long[5]; + + long BasePosition = MakeInt64From2xInt32(NvGpuEngine3dReg.ShaderAddress); + + for (int Index = 0; Index < 6; Index++) + { + int Control = ReadRegister(NvGpuEngine3dReg.ShaderNControl + Index * 0x10); + int Offset = ReadRegister(NvGpuEngine3dReg.ShaderNOffset + Index * 0x10); + + //Note: Vertex Program (B) is always enabled. + bool Enable = (Control & 1) != 0 || Index == 1; + + if (!Enable) + { + continue; + } + + long Tag = BasePosition + (uint)Offset; + + long Position = Gpu.GetCpuAddr(Tag); + + //TODO: Find a better way to calculate the size. + int Size = 0x20000; + + byte[] Code = AMemoryHelper.ReadBytes(Memory, Position, (uint)Size); + + GalShaderType ShaderType = GetTypeFromProgram(Index); + + Tags[(int)ShaderType] = Tag; + + Gpu.Renderer.CreateShader(Tag, ShaderType, Code); + Gpu.Renderer.BindShader(Tag); + } + + int RawSX = ReadRegister(NvGpuEngine3dReg.ViewportScaleX); + int RawSY = ReadRegister(NvGpuEngine3dReg.ViewportScaleY); + + float SX = BitConverter.Int32BitsToSingle(RawSX); + float SY = BitConverter.Int32BitsToSingle(RawSY); + + float SignX = MathF.Sign(SX); + float SignY = MathF.Sign(SY); + + Gpu.Renderer.SetUniform2F(GalConsts.FlipUniformName, SignX, SignY); + + return Tags; + } + + private static GalShaderType GetTypeFromProgram(int Program) + { + switch (Program) + { + case 0: + case 1: return GalShaderType.Vertex; + case 2: return GalShaderType.TessControl; + case 3: return GalShaderType.TessEvaluation; + case 4: return GalShaderType.Geometry; + case 5: return GalShaderType.Fragment; + } + + throw new ArgumentOutOfRangeException(nameof(Program)); + } + + private void SetAlphaBlending() + { + //TODO: Support independent blend properly. + bool Enable = (ReadRegister(NvGpuEngine3dReg.IBlendNEnable) & 1) != 0; + + Gpu.Renderer.SetBlendEnable(Enable); + + bool BlendSeparateAlpha = (ReadRegister(NvGpuEngine3dReg.IBlendNSeparateAlpha) & 1) != 0; + + GalBlendEquation EquationRgb = (GalBlendEquation)ReadRegister(NvGpuEngine3dReg.IBlendNEquationRgb); + + GalBlendFactor FuncSrcRgb = (GalBlendFactor)ReadRegister(NvGpuEngine3dReg.IBlendNFuncSrcRgb); + GalBlendFactor FuncDstRgb = (GalBlendFactor)ReadRegister(NvGpuEngine3dReg.IBlendNFuncDstRgb); + + if (BlendSeparateAlpha) + { + GalBlendEquation EquationAlpha = (GalBlendEquation)ReadRegister(NvGpuEngine3dReg.IBlendNEquationAlpha); + + GalBlendFactor FuncSrcAlpha = (GalBlendFactor)ReadRegister(NvGpuEngine3dReg.IBlendNFuncSrcAlpha); + GalBlendFactor FuncDstAlpha = (GalBlendFactor)ReadRegister(NvGpuEngine3dReg.IBlendNFuncDstAlpha); + + Gpu.Renderer.SetBlendSeparate( + EquationRgb, + EquationAlpha, + FuncSrcRgb, + FuncDstRgb, + FuncSrcAlpha, + FuncDstAlpha); + } + else + { + Gpu.Renderer.SetBlend(EquationRgb, FuncSrcRgb, FuncDstRgb); + } + } + + private void UploadTextures(AMemory Memory, long[] Tags) + { + long BaseShPosition = MakeInt64From2xInt32(NvGpuEngine3dReg.ShaderAddress); + + int TextureCbIndex = ReadRegister(NvGpuEngine3dReg.TextureCbIndex); + + long BasePosition = ConstBuffers[TextureCbIndex].Position; + + long Size = (uint)ConstBuffers[TextureCbIndex].Size; + + //Note: On the emulator renderer, Texture Unit 0 is + //reserved for drawing the frame buffer. + int TexIndex = 1; + + for (int Index = 0; Index < Tags.Length; Index++) + { + foreach (ShaderDeclInfo DeclInfo in Gpu.Renderer.GetTextureUsage(Tags[Index])) + { + long Position = BasePosition + Index * Size; + + UploadTexture(Memory, Position, TexIndex, DeclInfo.Index); + + Gpu.Renderer.SetUniform1(DeclInfo.Name, TexIndex); + + TexIndex++; + } + } + } + + private void UploadTexture(AMemory Memory, long BasePosition, int TexIndex, int HndIndex) + { + long Position = BasePosition + HndIndex * 4; + + int TextureHandle = Memory.ReadInt32(Position); + + int TicIndex = (TextureHandle >> 0) & 0xfffff; + int TscIndex = (TextureHandle >> 20) & 0xfff; + + TryGetCpuAddr(NvGpuEngine3dReg.TexHeaderPoolOffset, out long TicPosition); + TryGetCpuAddr(NvGpuEngine3dReg.TexSamplerPoolOffset, out long TscPosition); + + TicPosition += TicIndex * 0x20; + TscPosition += TscIndex * 0x20; + + GalTextureSampler Sampler = TextureFactory.MakeSampler(Gpu, Memory, TscPosition); + + long TextureAddress = Memory.ReadInt64(TicPosition + 4) & 0xffffffffffff; + + if (FrameBuffers.Contains(TextureAddress)) + { + //This texture is a frame buffer texture, + //we shouldn't read anything from memory and bind + //the frame buffer texture instead, since we're not + //really writing anything to memory. + Gpu.Renderer.BindFrameBufferTexture(TextureAddress, TexIndex, Sampler); + } + else + { + GalTexture Texture = TextureFactory.MakeTexture(Gpu, Memory, TicPosition); + + Gpu.Renderer.SetTextureAndSampler(TexIndex, Texture, Sampler); + Gpu.Renderer.BindTexture(TexIndex); + } + } + + private void UploadUniforms(AMemory Memory) + { + long BasePosition = MakeInt64From2xInt32(NvGpuEngine3dReg.ShaderAddress); + + for (int Index = 0; Index < 5; Index++) + { + int Control = ReadRegister(NvGpuEngine3dReg.ShaderNControl + (Index + 1) * 0x10); + int Offset = ReadRegister(NvGpuEngine3dReg.ShaderNOffset + (Index + 1) * 0x10); + + //Note: Vertex Program (B) is always enabled. + bool Enable = (Control & 1) != 0 || Index == 0; + + if (!Enable) + { + continue; + } + + for (int Cbuf = 0; Cbuf < ConstBuffers.Length; Cbuf++) + { + ConstBuffer Cb = ConstBuffers[Cbuf]; + + if (Cb.Enabled) + { + long CbPosition = Cb.Position + Index * Cb.Size; + + byte[] Data = AMemoryHelper.ReadBytes(Memory, CbPosition, (uint)Cb.Size); + + Gpu.Renderer.SetConstBuffer(BasePosition + (uint)Offset, Cbuf, Data); + } + } + } + } + + private void UploadVertexArrays(AMemory Memory) + { + long IndexPosition = MakeInt64From2xInt32(NvGpuEngine3dReg.IndexArrayAddress); + + int IndexSize = ReadRegister(NvGpuEngine3dReg.IndexArrayFormat); + int IndexFirst = ReadRegister(NvGpuEngine3dReg.IndexBatchFirst); + int IndexCount = ReadRegister(NvGpuEngine3dReg.IndexBatchCount); + + GalIndexFormat IndexFormat = (GalIndexFormat)IndexSize; + + IndexSize = 1 << IndexSize; + + if (IndexSize > 4) + { + throw new InvalidOperationException(); + } + + if (IndexSize != 0) + { + IndexPosition = Gpu.GetCpuAddr(IndexPosition); + + int BufferSize = IndexCount * IndexSize; + + byte[] Data = AMemoryHelper.ReadBytes(Memory, IndexPosition, BufferSize); + + Gpu.Renderer.SetIndexArray(Data, IndexFormat); + } + + List[] Attribs = new List[32]; + + for (int Attr = 0; Attr < 16; Attr++) + { + int Packed = ReadRegister(NvGpuEngine3dReg.VertexAttribNFormat + Attr); + + int ArrayIndex = Packed & 0x1f; + + if (Attribs[ArrayIndex] == null) + { + Attribs[ArrayIndex] = new List(); + } + + Attribs[ArrayIndex].Add(new GalVertexAttrib( + ((Packed >> 6) & 0x1) != 0, + (Packed >> 7) & 0x3fff, + (GalVertexAttribSize)((Packed >> 21) & 0x3f), + (GalVertexAttribType)((Packed >> 27) & 0x7), + ((Packed >> 31) & 0x1) != 0)); + } + + for (int Index = 0; Index < 32; Index++) + { + int Control = ReadRegister(NvGpuEngine3dReg.VertexArrayNControl + Index * 4); + + bool Enable = (Control & 0x1000) != 0; + + if (!Enable) + { + continue; + } + + long VertexPosition = MakeInt64From2xInt32(NvGpuEngine3dReg.VertexArrayNAddress + Index * 4); + long VertexEndPos = MakeInt64From2xInt32(NvGpuEngine3dReg.VertexArrayNEndAddr + Index * 4); + + long Size = (VertexEndPos - VertexPosition) + 1; + + int Stride = Control & 0xfff; + + VertexPosition = Gpu.GetCpuAddr(VertexPosition); + + byte[] Data = AMemoryHelper.ReadBytes(Memory, VertexPosition, Size); + + GalVertexAttrib[] AttribArray = Attribs[Index]?.ToArray() ?? new GalVertexAttrib[0]; + + Gpu.Renderer.SetVertexArray(Index, Stride, Data, AttribArray); + + int PrimCtrl = ReadRegister(NvGpuEngine3dReg.VertexBeginGl); + + GalPrimitiveType PrimType = (GalPrimitiveType)(PrimCtrl & 0xffff); + + if (IndexCount != 0) + { + Gpu.Renderer.DrawElements(Index, IndexFirst, PrimType); + } + else + { + Gpu.Renderer.DrawArrays(Index, PrimType); + } + } + } + + private void QueryControl(AMemory Memory, NsGpuPBEntry PBEntry) + { + if (TryGetCpuAddr(NvGpuEngine3dReg.QueryAddress, out long Position)) + { + int Seq = Registers[(int)NvGpuEngine3dReg.QuerySequence]; + int Ctrl = Registers[(int)NvGpuEngine3dReg.QueryControl]; + + int Mode = Ctrl & 3; + + if (Mode == 0) + { + //Write mode. + Memory.WriteInt32(Position, Seq); + } + } + + WriteRegister(PBEntry); + } + + private void CbData(AMemory Memory, NsGpuPBEntry PBEntry) + { + if (TryGetCpuAddr(NvGpuEngine3dReg.ConstBufferNAddress, out long Position)) + { + int Offset = ReadRegister(NvGpuEngine3dReg.ConstBufferNOffset); + + foreach (int Arg in PBEntry.Arguments) + { + Memory.WriteInt32(Position + Offset, Arg); + + Offset += 4; + } + + WriteRegister(NvGpuEngine3dReg.ConstBufferNOffset, Offset); + } + } + + private void CbBind(AMemory Memory, NsGpuPBEntry PBEntry) + { + int Index = PBEntry.Arguments[0]; + + bool Enabled = (Index & 1) != 0; + + Index = (Index >> 4) & 0x1f; + + if (TryGetCpuAddr(NvGpuEngine3dReg.ConstBufferNAddress, out long Position)) + { + ConstBuffers[Index].Position = Position; + ConstBuffers[Index].Enabled = Enabled; + + ConstBuffers[Index].Size = ReadRegister(NvGpuEngine3dReg.ConstBufferNSize); + } + } + + private int ReadCb(AMemory Memory, int Cbuf, int Offset) + { + long Position = ConstBuffers[Cbuf].Position; + + int Value = Memory.ReadInt32(Position + Offset); + + return Value; + } + + private bool TryGetCpuAddr(NvGpuEngine3dReg Reg, out long Position) + { + Position = MakeInt64From2xInt32(Reg); + + Position = Gpu.GetCpuAddr(Position); + + return Position != -1; + } + + private long MakeInt64From2xInt32(NvGpuEngine3dReg Reg) + { + return + (long)Registers[(int)Reg + 0] << 32 | + (uint)Registers[(int)Reg + 1]; + } + + private void WriteRegister(NsGpuPBEntry PBEntry) + { + int ArgsCount = PBEntry.Arguments.Count; + + if (ArgsCount > 0) + { + Registers[PBEntry.Method] = PBEntry.Arguments[ArgsCount - 1]; + } + } + + private int ReadRegister(NvGpuEngine3dReg Reg) + { + return Registers[(int)Reg]; + } + + private void WriteRegister(NvGpuEngine3dReg Reg, int Value) + { + Registers[(int)Reg] = Value; + } + + public bool IsFrameBufferPosition(long Position) + { + return FrameBuffers.Contains(Position); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gpu/NvGpuEngine3dReg.cs b/Ryujinx.Graphics/Gpu/NvGpuEngine3dReg.cs new file mode 100644 index 000000000..605ca9dab --- /dev/null +++ b/Ryujinx.Graphics/Gpu/NvGpuEngine3dReg.cs @@ -0,0 +1,59 @@ +namespace Ryujinx.Graphics.Gpu +{ + enum NvGpuEngine3dReg + { + FrameBufferNAddress = 0x200, + FrameBufferNWidth = 0x202, + FrameBufferNHeight = 0x203, + FrameBufferNFormat = 0x204, + ViewportScaleX = 0x280, + ViewportScaleY = 0x281, + ViewportScaleZ = 0x282, + ViewportTranslateX = 0x283, + ViewportTranslateY = 0x284, + ViewportTranslateZ = 0x285, + VertexAttribNFormat = 0x458, + IBlendEnable = 0x4b9, + BlendSeparateAlpha = 0x4cf, + BlendEquationRgb = 0x4d0, + BlendFuncSrcRgb = 0x4d1, + BlendFuncDstRgb = 0x4d2, + BlendEquationAlpha = 0x4d3, + BlendFuncSrcAlpha = 0x4d4, + BlendFuncDstAlpha = 0x4d6, + BlendEnableMaster = 0x4d7, + IBlendNEnable = 0x4d8, + VertexArrayElemBase = 0x50d, + TexHeaderPoolOffset = 0x55d, + TexSamplerPoolOffset = 0x557, + ShaderAddress = 0x582, + VertexBeginGl = 0x586, + IndexArrayAddress = 0x5f2, + IndexArrayEndAddr = 0x5f4, + IndexArrayFormat = 0x5f6, + IndexBatchFirst = 0x5f7, + IndexBatchCount = 0x5f8, + QueryAddress = 0x6c0, + QuerySequence = 0x6c2, + QueryControl = 0x6c3, + VertexArrayNControl = 0x700, + VertexArrayNAddress = 0x701, + VertexArrayNDivisor = 0x703, + IBlendNSeparateAlpha = 0x780, + IBlendNEquationRgb = 0x781, + IBlendNFuncSrcRgb = 0x782, + IBlendNFuncDstRgb = 0x783, + IBlendNEquationAlpha = 0x784, + IBlendNFuncSrcAlpha = 0x785, + IBlendNFuncDstAlpha = 0x786, + VertexArrayNEndAddr = 0x7c0, + ShaderNControl = 0x800, + ShaderNOffset = 0x801, + ShaderNMaxGprs = 0x803, + ShaderNType = 0x804, + ConstBufferNSize = 0x8e0, + ConstBufferNAddress = 0x8e1, + ConstBufferNOffset = 0x8e3, + TextureCbIndex = 0x982 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gpu/NvGpuFifo.cs b/Ryujinx.Graphics/Gpu/NvGpuFifo.cs new file mode 100644 index 000000000..df765895c --- /dev/null +++ b/Ryujinx.Graphics/Gpu/NvGpuFifo.cs @@ -0,0 +1,171 @@ +using ChocolArm64.Memory; +using System.Collections.Concurrent; + +namespace Ryujinx.Graphics.Gpu +{ + public class NvGpuFifo + { + private const int MacrosCount = 0x80; + private const int MacroIndexMask = MacrosCount - 1; + + private NsGpu Gpu; + + private ConcurrentQueue<(AMemory, NsGpuPBEntry)> BufferQueue; + + private NvGpuEngine[] SubChannels; + + private struct CachedMacro + { + public long Position { get; private set; } + + private MacroInterpreter Interpreter; + + public CachedMacro(NvGpuFifo PFifo, INvGpuEngine Engine, long Position) + { + this.Position = Position; + + Interpreter = new MacroInterpreter(PFifo, Engine); + } + + public void PushParam(int Param) + { + Interpreter?.Fifo.Enqueue(Param); + } + + public void Execute(AMemory Memory, int Param) + { + Interpreter?.Execute(Memory, Position, Param); + } + } + + private long CurrMacroPosition; + private int CurrMacroBindIndex; + + private CachedMacro[] Macros; + + public NvGpuFifo(NsGpu Gpu) + { + this.Gpu = Gpu; + + BufferQueue = new ConcurrentQueue<(AMemory, NsGpuPBEntry)>(); + + SubChannels = new NvGpuEngine[8]; + + Macros = new CachedMacro[MacrosCount]; + } + + public void PushBuffer(AMemory Memory, NsGpuPBEntry[] Buffer) + { + foreach (NsGpuPBEntry PBEntry in Buffer) + { + BufferQueue.Enqueue((Memory, PBEntry)); + } + } + + public void DispatchCalls() + { + while (Step()); + } + + public bool Step() + { + if (BufferQueue.TryDequeue(out (AMemory Memory, NsGpuPBEntry PBEntry) Tuple)) + { + CallMethod(Tuple.Memory, Tuple.PBEntry); + + return true; + } + + return false; + } + + private void CallMethod(AMemory Memory, NsGpuPBEntry PBEntry) + { + if (PBEntry.Method < 0x80) + { + switch ((NvGpuFifoMeth)PBEntry.Method) + { + case NvGpuFifoMeth.BindChannel: + { + NvGpuEngine Engine = (NvGpuEngine)PBEntry.Arguments[0]; + + SubChannels[PBEntry.SubChannel] = Engine; + + break; + } + + case NvGpuFifoMeth.SetMacroUploadAddress: + { + CurrMacroPosition = (long)((ulong)PBEntry.Arguments[0] << 2); + + break; + } + + case NvGpuFifoMeth.SendMacroCodeData: + { + long Position = Gpu.GetCpuAddr(CurrMacroPosition); + + foreach (int Arg in PBEntry.Arguments) + { + Memory.WriteInt32(Position, Arg); + + CurrMacroPosition += 4; + + Position += 4; + } + break; + } + + case NvGpuFifoMeth.SetMacroBindingIndex: + { + CurrMacroBindIndex = PBEntry.Arguments[0]; + + break; + } + + case NvGpuFifoMeth.BindMacro: + { + long Position = (long)((ulong)PBEntry.Arguments[0] << 2); + + Position = Gpu.GetCpuAddr(Position); + + Macros[CurrMacroBindIndex] = new CachedMacro(this, Gpu.Engine3d, Position); + + break; + } + } + } + else + { + switch (SubChannels[PBEntry.SubChannel]) + { + case NvGpuEngine._3d: Call3dMethod(Memory, PBEntry); break; + } + } + } + + private void Call3dMethod(AMemory Memory, NsGpuPBEntry PBEntry) + { + if (PBEntry.Method < 0xe00) + { + Gpu.Engine3d.CallMethod(Memory, PBEntry); + } + else + { + int MacroIndex = (PBEntry.Method >> 1) & MacroIndexMask; + + if ((PBEntry.Method & 1) != 0) + { + foreach (int Arg in PBEntry.Arguments) + { + Macros[MacroIndex].PushParam(Arg); + } + } + else + { + Macros[MacroIndex].Execute(Memory, PBEntry.Arguments[0]); + } + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gpu/NvGpuFifoMeth.cs b/Ryujinx.Graphics/Gpu/NvGpuFifoMeth.cs new file mode 100644 index 000000000..4287e2500 --- /dev/null +++ b/Ryujinx.Graphics/Gpu/NvGpuFifoMeth.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Graphics.Gpu +{ + enum NvGpuFifoMeth + { + BindChannel = 0, + SetMacroUploadAddress = 0x45, + SendMacroCodeData = 0x46, + SetMacroBindingIndex = 0x47, + BindMacro = 0x48 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gpu/NvGpuMethod.cs b/Ryujinx.Graphics/Gpu/NvGpuMethod.cs new file mode 100644 index 000000000..2923ddff0 --- /dev/null +++ b/Ryujinx.Graphics/Gpu/NvGpuMethod.cs @@ -0,0 +1,6 @@ +using ChocolArm64.Memory; + +namespace Ryujinx.Graphics.Gpu +{ + delegate void NvGpuMethod(AMemory Memory, NsGpuPBEntry PBEntry); +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gpu/NvGpuPushBuffer.cs b/Ryujinx.Graphics/Gpu/NvGpuPushBuffer.cs new file mode 100644 index 000000000..8cbb3288e --- /dev/null +++ b/Ryujinx.Graphics/Gpu/NvGpuPushBuffer.cs @@ -0,0 +1,101 @@ +using System.Collections.Generic; +using System.IO; + +namespace Ryujinx.Graphics.Gpu +{ + public static class NvGpuPushBuffer + { + private enum SubmissionMode + { + Incrementing = 1, + NonIncrementing = 3, + Immediate = 4, + IncrementOnce = 5 + } + + public static NsGpuPBEntry[] Decode(byte[] Data) + { + using (MemoryStream MS = new MemoryStream(Data)) + { + BinaryReader Reader = new BinaryReader(MS); + + List PushBuffer = new List(); + + bool CanRead() => MS.Position + 4 <= MS.Length; + + while (CanRead()) + { + int Packed = Reader.ReadInt32(); + + int Meth = (Packed >> 0) & 0x1fff; + int SubC = (Packed >> 13) & 7; + int Args = (Packed >> 16) & 0x1fff; + int Mode = (Packed >> 29) & 7; + + switch ((SubmissionMode)Mode) + { + case SubmissionMode.Incrementing: + { + for (int Index = 0; Index < Args && CanRead(); Index++, Meth++) + { + PushBuffer.Add(new NsGpuPBEntry(Meth, SubC, Reader.ReadInt32())); + } + + break; + } + + case SubmissionMode.NonIncrementing: + { + int[] Arguments = new int[Args]; + + for (int Index = 0; Index < Arguments.Length; Index++) + { + if (!CanRead()) + { + break; + } + + Arguments[Index] = Reader.ReadInt32(); + } + + PushBuffer.Add(new NsGpuPBEntry(Meth, SubC, Arguments)); + + break; + } + + case SubmissionMode.Immediate: + { + PushBuffer.Add(new NsGpuPBEntry(Meth, SubC, Args)); + + break; + } + + case SubmissionMode.IncrementOnce: + { + if (CanRead()) + { + PushBuffer.Add(new NsGpuPBEntry(Meth, SubC, Reader.ReadInt32())); + } + + if (CanRead() && Args > 1) + { + int[] Arguments = new int[Args - 1]; + + for (int Index = 0; Index < Arguments.Length && CanRead(); Index++) + { + Arguments[Index] = Reader.ReadInt32(); + } + + PushBuffer.Add(new NsGpuPBEntry(Meth + 1, SubC, Arguments)); + } + + break; + } + } + } + + return PushBuffer.ToArray(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gpu/Texture.cs b/Ryujinx.Graphics/Gpu/Texture.cs new file mode 100644 index 000000000..cbfa683dc --- /dev/null +++ b/Ryujinx.Graphics/Gpu/Texture.cs @@ -0,0 +1,55 @@ +using Ryujinx.Graphics.Gal; + +namespace Ryujinx.Graphics.Gpu +{ + public struct Texture + { + public long Position { get; private set; } + + public int Width { get; private set; } + public int Height { get; private set; } + public int Pitch { get; private set; } + + public int BlockHeight { get; private set; } + + public TextureSwizzle Swizzle { get; private set; } + + public GalTextureFormat Format { get; private set; } + + public Texture( + long Position, + int Width, + int Height) + { + this.Position = Position; + this.Width = Width; + this.Height = Height; + + Pitch = 0; + + BlockHeight = 16; + + Swizzle = TextureSwizzle.BlockLinear; + + Format = GalTextureFormat.A8B8G8R8; + } + + public Texture( + long Position, + int Width, + int Height, + int Pitch, + int BlockHeight, + TextureSwizzle Swizzle, + GalTextureFormat Format) + { + this.Position = Position; + this.Width = Width; + this.Height = Height; + this.Pitch = Pitch; + this.BlockHeight = BlockHeight; + this.Swizzle = Swizzle; + this.Format = Format; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gpu/TextureFactory.cs b/Ryujinx.Graphics/Gpu/TextureFactory.cs new file mode 100644 index 000000000..7f8580d98 --- /dev/null +++ b/Ryujinx.Graphics/Gpu/TextureFactory.cs @@ -0,0 +1,86 @@ +using ChocolArm64.Memory; +using Ryujinx.Graphics.Gal; +using System; + +namespace Ryujinx.Graphics.Gpu +{ + static class TextureFactory + { + public static GalTexture MakeTexture(NsGpu Gpu, AMemory Memory, long TicPosition) + { + int[] Tic = ReadWords(Memory, TicPosition, 8); + + GalTextureFormat Format = (GalTextureFormat)(Tic[0] & 0x7f); + + long TextureAddress = (uint)Tic[1]; + + TextureAddress |= (long)((ushort)Tic[2]) << 32; + + TextureAddress = Gpu.GetCpuAddr(TextureAddress); + + TextureSwizzle Swizzle = (TextureSwizzle)((Tic[2] >> 21) & 7); + + int Pitch = (Tic[3] & 0xffff) << 5; + + int BlockHeightLog2 = (Tic[3] >> 3) & 7; + + int BlockHeight = 1 << BlockHeightLog2; + + int Width = (Tic[4] & 0xffff) + 1; + int Height = (Tic[5] & 0xffff) + 1; + + Texture Texture = new Texture( + TextureAddress, + Width, + Height, + Pitch, + BlockHeight, + Swizzle, + Format); + + byte[] Data = TextureReader.Read(Memory, Texture); + + return new GalTexture(Data, Width, Height, Format); + } + + public static GalTextureSampler MakeSampler(NsGpu Gpu, AMemory Memory, long TscPosition) + { + int[] Tsc = ReadWords(Memory, TscPosition, 8); + + GalTextureWrap AddressU = (GalTextureWrap)((Tsc[0] >> 0) & 7); + GalTextureWrap AddressV = (GalTextureWrap)((Tsc[0] >> 3) & 7); + GalTextureWrap AddressP = (GalTextureWrap)((Tsc[0] >> 6) & 7); + + GalTextureFilter MagFilter = (GalTextureFilter) ((Tsc[1] >> 0) & 3); + GalTextureFilter MinFilter = (GalTextureFilter) ((Tsc[1] >> 4) & 3); + GalTextureMipFilter MipFilter = (GalTextureMipFilter)((Tsc[1] >> 6) & 3); + + GalColorF BorderColor = new GalColorF( + BitConverter.Int32BitsToSingle(Tsc[4]), + BitConverter.Int32BitsToSingle(Tsc[5]), + BitConverter.Int32BitsToSingle(Tsc[6]), + BitConverter.Int32BitsToSingle(Tsc[7])); + + return new GalTextureSampler( + AddressU, + AddressV, + AddressP, + MinFilter, + MagFilter, + MipFilter, + BorderColor); + } + + private static int[] ReadWords(AMemory Memory, long Position, int Count) + { + int[] Words = new int[Count]; + + for (int Index = 0; Index < Count; Index++, Position += 4) + { + Words[Index] = Memory.ReadInt32(Position); + } + + return Words; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gpu/TextureReader.cs b/Ryujinx.Graphics/Gpu/TextureReader.cs new file mode 100644 index 000000000..4c3b4fb17 --- /dev/null +++ b/Ryujinx.Graphics/Gpu/TextureReader.cs @@ -0,0 +1,160 @@ +using ChocolArm64.Memory; +using Ryujinx.Graphics.Gal; +using System; + +namespace Ryujinx.Graphics.Gpu +{ + public static class TextureReader + { + public static byte[] Read(AMemory Memory, Texture Texture) + { + switch (Texture.Format) + { + case GalTextureFormat.A8B8G8R8: return Read4Bpp (Memory, Texture); + case GalTextureFormat.A1B5G5R5: return Read2Bpp (Memory, Texture); + case GalTextureFormat.B5G6R5: return Read2Bpp (Memory, Texture); + case GalTextureFormat.BC1: return Read8Bpt4x4 (Memory, Texture); + case GalTextureFormat.BC2: return Read16Bpt4x4(Memory, Texture); + case GalTextureFormat.BC3: return Read16Bpt4x4(Memory, Texture); + case GalTextureFormat.BC4: return Read8Bpt4x4 (Memory, Texture); + case GalTextureFormat.BC5: return Read16Bpt4x4(Memory, Texture); + } + + throw new NotImplementedException(Texture.Format.ToString()); + } + + private unsafe static byte[] Read2Bpp(AMemory Memory, Texture Texture) + { + int Width = Texture.Width; + int Height = Texture.Height; + + byte[] Output = new byte[Width * Height * 2]; + + ISwizzle Swizzle = GetSwizzle(Texture, Width, 2); + + fixed (byte* BuffPtr = Output) + { + long OutOffs = 0; + + for (int Y = 0; Y < Height; Y++) + for (int X = 0; X < Width; X++) + { + long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y); + + short Pixel = Memory.ReadInt16Unchecked(Texture.Position + Offset); + + *(short*)(BuffPtr + OutOffs) = Pixel; + + OutOffs += 2; + } + } + + return Output; + } + + private unsafe static byte[] Read4Bpp(AMemory Memory, Texture Texture) + { + int Width = Texture.Width; + int Height = Texture.Height; + + byte[] Output = new byte[Width * Height * 4]; + + ISwizzle Swizzle = GetSwizzle(Texture, Width, 4); + + fixed (byte* BuffPtr = Output) + { + long OutOffs = 0; + + for (int Y = 0; Y < Height; Y++) + for (int X = 0; X < Width; X++) + { + long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y); + + int Pixel = Memory.ReadInt32Unchecked(Texture.Position + Offset); + + *(int*)(BuffPtr + OutOffs) = Pixel; + + OutOffs += 4; + } + } + + return Output; + } + + private unsafe static byte[] Read8Bpt4x4(AMemory Memory, Texture Texture) + { + int Width = (Texture.Width + 3) / 4; + int Height = (Texture.Height + 3) / 4; + + byte[] Output = new byte[Width * Height * 8]; + + ISwizzle Swizzle = GetSwizzle(Texture, Width, 8); + + fixed (byte* BuffPtr = Output) + { + long OutOffs = 0; + + for (int Y = 0; Y < Height; Y++) + for (int X = 0; X < Width; X++) + { + long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y); + + long Tile = Memory.ReadInt64Unchecked(Texture.Position + Offset); + + *(long*)(BuffPtr + OutOffs) = Tile; + + OutOffs += 8; + } + } + + return Output; + } + + private unsafe static byte[] Read16Bpt4x4(AMemory Memory, Texture Texture) + { + int Width = (Texture.Width + 3) / 4; + int Height = (Texture.Height + 3) / 4; + + byte[] Output = new byte[Width * Height * 16]; + + ISwizzle Swizzle = GetSwizzle(Texture, Width, 16); + + fixed (byte* BuffPtr = Output) + { + long OutOffs = 0; + + for (int Y = 0; Y < Height; Y++) + for (int X = 0; X < Width; X++) + { + long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y); + + long Tile0 = Memory.ReadInt64Unchecked(Texture.Position + Offset + 0); + long Tile1 = Memory.ReadInt64Unchecked(Texture.Position + Offset + 8); + + *(long*)(BuffPtr + OutOffs + 0) = Tile0; + *(long*)(BuffPtr + OutOffs + 8) = Tile1; + + OutOffs += 16; + } + } + + return Output; + } + + private static ISwizzle GetSwizzle(Texture Texture, int Width, int Bpp) + { + switch (Texture.Swizzle) + { + case TextureSwizzle.Pitch: + case TextureSwizzle.PitchColorKey: + return new LinearSwizzle(Texture.Pitch, Bpp); + + case TextureSwizzle.BlockLinear: + case TextureSwizzle.BlockLinearColorKey: + return new BlockLinearSwizzle(Width, Bpp, Texture.BlockHeight); + } + + throw new NotImplementedException(Texture.Swizzle.ToString()); + } + } +} diff --git a/Ryujinx.Graphics/Gpu/TextureSwizzle.cs b/Ryujinx.Graphics/Gpu/TextureSwizzle.cs new file mode 100644 index 000000000..7d99279cd --- /dev/null +++ b/Ryujinx.Graphics/Gpu/TextureSwizzle.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Graphics.Gpu +{ + public enum TextureSwizzle + { + _1dBuffer = 0, + PitchColorKey = 1, + Pitch = 2, + BlockLinear = 3, + BlockLinearColorKey = 4 + } +} \ No newline at end of file diff --git a/Ryujinx.Tests/Cpu/CpuTest.cs b/Ryujinx.Tests/Cpu/CpuTest.cs index b5bbdbc17..14ef0a15d 100644 --- a/Ryujinx.Tests/Cpu/CpuTest.cs +++ b/Ryujinx.Tests/Cpu/CpuTest.cs @@ -1,7 +1,9 @@ using ChocolArm64; using ChocolArm64.Memory; using ChocolArm64.State; + using NUnit.Framework; + using System.Threading; namespace Ryujinx.Tests.Cpu @@ -51,13 +53,14 @@ namespace Ryujinx.Tests.Cpu Position += 4; } - protected void SetThreadState(ulong X0 = 0, ulong X1 = 0, ulong X2 = 0, ulong X31 = 0, + protected void SetThreadState(ulong X0 = 0, ulong X1 = 0, ulong X2 = 0, ulong X3 = 0, ulong X31 = 0, AVec V0 = default(AVec), AVec V1 = default(AVec), AVec V2 = default(AVec), bool Overflow = false, bool Carry = false, bool Zero = false, bool Negative = false, int Fpcr = 0x0) { Thread.ThreadState.X0 = X0; Thread.ThreadState.X1 = X1; Thread.ThreadState.X2 = X2; + Thread.ThreadState.X3 = X3; Thread.ThreadState.X31 = X31; Thread.ThreadState.V0 = V0; Thread.ThreadState.V1 = V1; @@ -87,14 +90,14 @@ namespace Ryujinx.Tests.Cpu } protected AThreadState SingleOpcode(uint Opcode, - ulong X0 = 0, ulong X1 = 0, ulong X2 = 0, ulong X31 = 0, + ulong X0 = 0, ulong X1 = 0, ulong X2 = 0, ulong X3 = 0, ulong X31 = 0, AVec V0 = default(AVec), AVec V1 = default(AVec), AVec V2 = default(AVec), bool Overflow = false, bool Carry = false, bool Zero = false, bool Negative = false, int Fpcr = 0x0) { this.Opcode(Opcode); this.Opcode(0xD4200000); // BRK #0 this.Opcode(0xD65F03C0); // RET - SetThreadState(X0, X1, X2, X31, V0, V1, V2, Overflow, Carry, Zero, Negative, Fpcr); + SetThreadState(X0, X1, X2, X3, X31, V0, V1, V2, Overflow, Carry, Zero, Negative, Fpcr); ExecuteOpcodes(); return GetThreadState(); diff --git a/Ryujinx.Tests/Cpu/CpuTestAlu.cs b/Ryujinx.Tests/Cpu/CpuTestAlu.cs index 8116fc7c7..564fadec2 100644 --- a/Ryujinx.Tests/Cpu/CpuTestAlu.cs +++ b/Ryujinx.Tests/Cpu/CpuTestAlu.cs @@ -1,162 +1,331 @@ +//#define Alu + using ChocolArm64.State; + using NUnit.Framework; namespace Ryujinx.Tests.Cpu { - public class CpuTestAlu : CpuTest + using Tester; + using Tester.Types; + + [Category("Alu"), Ignore("Tested: first half of 2018.")] + public sealed class CpuTestAlu : CpuTest { - [TestCase(0x9A020020u, 2u, 3u, true, 6u)] - [TestCase(0x9A020020u, 2u, 3u, false, 5u)] - [TestCase(0x1A020020u, 2u, 3u, true, 6u)] - [TestCase(0x1A020020u, 2u, 3u, false, 5u)] - [TestCase(0x1A020020u, 0xFFFFFFFFu, 0x2u, false, 0x1u)] - public void Adc(uint Opcode, uint A, uint B, bool CarryState, uint Result) +#if Alu + [SetUp] + public void SetupTester() { - // ADC (X0/W0), (X1/W1), (X2/W2) - AThreadState ThreadState = SingleOpcode(Opcode, X1: A, X2: B, Carry: CarryState); - Assert.AreEqual(Result, ThreadState.X0); + AArch64.TakeReset(false); } - [TestCase(0x3A020020u, 2u, 3u, false, false, false, false, 5u)] - [TestCase(0x3A020020u, 2u, 3u, true, false, false, false, 6u)] - [TestCase(0xBA020020u, 2u, 3u, false, false, false, false, 5u)] - [TestCase(0xBA020020u, 2u, 3u, true, false, false, false, 6u)] - [TestCase(0x3A020020u, 0xFFFFFFFEu, 0x1u, true, false, true, true, 0x0u)] - [TestCase(0x3A020020u, 0xFFFFFFFFu, 0xFFFFFFFFu, true, true, false, true, 0xFFFFFFFFu)] - public void Adcs(uint Opcode, uint A, uint B, bool CarryState, bool Negative, bool Zero, bool Carry, uint Result) + [Test, Description("CLS , ")] + public void Cls_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(256)] ulong Xn) { - //ADCS (X0/W0), (X1, W1), (X2/W2) - AThreadState ThreadState = SingleOpcode(Opcode, X1: A, X2: B, Carry: CarryState); - Assert.Multiple(() => + uint Opcode = 0xDAC01400; // CLS X0, X0 + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X31: _X31); + + if (Rd != 31) { - Assert.IsFalse(ThreadState.Overflow); - Assert.AreEqual(Negative, ThreadState.Negative); - Assert.AreEqual(Zero, ThreadState.Zero); - Assert.AreEqual(Carry, ThreadState.Carry); - Assert.AreEqual(Result, ThreadState.X0); - }); - } - - [Test] - public void Add() - { - // ADD X0, X1, X2 - AThreadState ThreadState = SingleOpcode(0x8B020020, X1: 1, X2: 2); - Assert.AreEqual(3, ThreadState.X0); - } + Bits Op = new Bits(Opcode); - [TestCase(2u, false, false)] - [TestCase(5u, false, false)] - [TestCase(7u, false, false)] - [TestCase(0xFFFFFFFFu, false, true )] - [TestCase(0xFFFFFFFBu, true, true )] - public void Adds(uint A, bool Zero, bool Carry) - { - //ADDS WZR, WSP, #5 - AThreadState ThreadState = SingleOpcode(0x310017FF, X31: A); - Assert.Multiple(() => + AArch64.X((int)Rn, new Bits(Xn)); + Base.Cls(Op[31], Op[9, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else { - Assert.IsFalse(ThreadState.Negative); - Assert.IsFalse(ThreadState.Overflow); - Assert.AreEqual(Zero, ThreadState.Zero); - Assert.AreEqual(Carry, ThreadState.Carry); - Assert.AreEqual(A, ThreadState.X31); - }); + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } } - [TestCase(0xFFFFFFFFu, 0xFFFFFFFFu, 0xFFFFFFFFul, true, false)] - [TestCase(0xFFFFFFFFu, 0x00000000u, 0x00000000ul, false, true)] - [TestCase(0x12345678u, 0x7324A993u, 0x12240010ul, false, false)] - public void Ands(uint A, uint B, ulong Result, bool Negative, bool Zero) + [Test, Description("CLS , ")] + public void Cls_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(256)] uint Wn) { - // ANDS W0, W1, W2 - uint Opcode = 0x6A020020; - AThreadState ThreadState = SingleOpcode(Opcode, X1: A, X2: B); - Assert.Multiple(() => + uint Opcode = 0x5AC01400; // CLS W0, W0 + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X31: _W31); + + if (Rd != 31) { - Assert.AreEqual(Result, ThreadState.X0); - Assert.AreEqual(Negative, ThreadState.Negative); - Assert.AreEqual(Zero, ThreadState.Zero); - }); - } + Bits Op = new Bits(Opcode); - [TestCase(0x0000FF44u, 0x00000004u, 0x00000FF4u)] - [TestCase(0x00000000u, 0x00000004u, 0x00000000u)] - [TestCase(0x0000FF44u, 0x00000008u, 0x000000FFu)] - [TestCase(0xFFFFFFFFu, 0x00000004u, 0xFFFFFFFFu)] - [TestCase(0xFFFFFFFFu, 0x00000008u, 0xFFFFFFFFu)] - [TestCase(0xFFFFFFFFu, 0x00000020u, 0xFFFFFFFFu)] - [TestCase(0x0FFFFFFFu, 0x0000001Cu, 0x00000000u)] - [TestCase(0x80000000u, 0x0000001Fu, 0xFFFFFFFFu)] - [TestCase(0xCAFE0000u, 0x00000020u, 0xCAFE0000u)] - public void Asrv32(uint A, uint ShiftValue, uint Result) - { - // ASRV W0, W1, W2 - AThreadState ThreadState = SingleOpcode(0x1AC22820, X1: A, X2: ShiftValue); - Assert.AreEqual(Result, ThreadState.X0); - } + AArch64.X((int)Rn, new Bits(Wn)); + Base.Cls(Op[31], Op[9, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - [TestCase(0x000000000000FF44ul, 0x00000004u, 0x0000000000000FF4ul)] - [TestCase(0x0000000000000000ul, 0x00000004u, 0x0000000000000000ul)] - [TestCase(0x000000000000FF44ul, 0x00000008u, 0x00000000000000FFul)] - [TestCase(0x00000000FFFFFFFFul, 0x00000004u, 0x000000000FFFFFFFul)] - [TestCase(0x00000000FFFFFFFFul, 0x00000008u, 0x0000000000FFFFFFul)] - [TestCase(0x00000000FFFFFFFFul, 0x00000020u, 0x0000000000000000ul)] - [TestCase(0x000000000FFFFFFFul, 0x0000001Cu, 0x0000000000000000ul)] - [TestCase(0x000CC4488FFFFFFFul, 0x0000001Cu, 0x0000000000CC4488ul)] - [TestCase(0xFFFFFFFFFFFFFFFFul, 0x0000001Cu, 0xFFFFFFFFFFFFFFFFul)] - [TestCase(0x8000000000000000ul, 0x0000003Fu, 0xFFFFFFFFFFFFFFFFul)] - [TestCase(0xCAFE000000000000ul, 0x00000040u, 0xCAFE000000000000ul)] - public void Asrv64(ulong A, uint ShiftValue, ulong Result) - { - // ASRV X0, X1, X2 - AThreadState ThreadState = SingleOpcode(0x9AC22820, X1: A, X2: ShiftValue); - Assert.AreEqual(Result, ThreadState.X0); - } - - [TestCase(0x01010101u, 0x3200C3E2u)] - [TestCase(0x00F000F0u, 0x320C8FE2u)] - [TestCase(0x00000001u, 0x320003E2u)] - public void OrrBitmasks(uint Bitmask, uint Opcode) - { - // ORR W2, WZR, #Bitmask - Assert.AreEqual(Bitmask, SingleOpcode(Opcode).X2); - } - - [Test] - public void RevX0X0() - { - // REV X0, X0 - AThreadState ThreadState = SingleOpcode(0xDAC00C00, X0: 0xAABBCCDDEEFF1100); - Assert.AreEqual(0x0011FFEEDDCCBBAA, ThreadState.X0); - } - - [Test] - public void RevW1W1() - { - // REV W1, W1 - AThreadState ThreadState = SingleOpcode(0x5AC00821, X1: 0x12345678); - Assert.AreEqual(0x78563412, ThreadState.X1); - } - - [TestCase(0x7A020020u, 4u, 2u, false, false, false, true, 1u)] - [TestCase(0x7A020020u, 4u, 2u, true, false, false, true, 2u)] - [TestCase(0xFA020020u, 4u, 2u, false, false, false, true, 1u)] - [TestCase(0xFA020020u, 4u, 2u, true, false, false, true, 2u)] - [TestCase(0x7A020020u, 4u, 4u, false, true, false, false, 0xFFFFFFFFu)] - [TestCase(0x7A020020u, 4u, 4u, true, false, true, true, 0x0u)] - public void Sbcs(uint Opcode, uint A, uint B, bool CarryState, bool Negative, bool Zero, bool Carry, uint Result) - { - //SBCS (X0/W0), (X1, W1), (X2/W2) - AThreadState ThreadState = SingleOpcode(Opcode, X1: A, X2: B, Carry: CarryState); - Assert.Multiple(() => + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else { - Assert.IsFalse(ThreadState.Overflow); - Assert.AreEqual(Negative, ThreadState.Negative); - Assert.AreEqual(Zero, ThreadState.Zero); - Assert.AreEqual(Carry, ThreadState.Carry); - Assert.AreEqual(Result, ThreadState.X0); - }); + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } } + + [Test, Description("CLZ , ")] + public void Clz_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(256)] ulong Xn) + { + uint Opcode = 0xDAC01000; // CLZ X0, X0 + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X31: _X31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Xn)); + Base.Clz(Op[31], Op[9, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + } + + [Test, Description("CLZ , ")] + public void Clz_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(256)] uint Wn) + { + uint Opcode = 0x5AC01000; // CLZ W0, W0 + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X31: _W31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Wn)); + Base.Clz(Op[31], Op[9, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + } + + [Test, Description("RBIT , ")] + public void Rbit_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(256)] ulong Xn) + { + uint Opcode = 0xDAC00000; // RBIT X0, X0 + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X31: _X31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Xn)); + Base.Rbit(Op[31], Op[9, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + } + + [Test, Description("RBIT , ")] + public void Rbit_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(256)] uint Wn) + { + uint Opcode = 0x5AC00000; // RBIT W0, W0 + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X31: _W31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Wn)); + Base.Rbit(Op[31], Op[9, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + } + + [Test, Description("REV16 , ")] + public void Rev16_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(256)] ulong Xn) + { + uint Opcode = 0xDAC00400; // REV16 X0, X0 + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X31: _X31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Xn)); + Base.Rev16(Op[31], Op[9, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + } + + [Test, Description("REV16 , ")] + public void Rev16_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(256)] uint Wn) + { + uint Opcode = 0x5AC00400; // REV16 W0, W0 + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X31: _W31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Wn)); + Base.Rev16(Op[31], Op[9, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + } + + [Test, Description("REV32 , ")] + public void Rev32_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(256)] ulong Xn) + { + uint Opcode = 0xDAC00800; // REV32 X0, X0 + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X31: _X31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Xn)); + Base.Rev32(Op[31], Op[9, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + } + + [Test, Description("REV , ")] + public void Rev32_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(256)] uint Wn) + { + uint Opcode = 0x5AC00800; // REV W0, W0 + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X31: _W31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Wn)); + Base.Rev32(Op[31], Op[9, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + } + + [Test, Description("REV64 , ")] + public void Rev64_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(256)] ulong Xn) + { + uint Opcode = 0xDAC00C00; // REV64 X0, X0 + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X31: _X31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Xn)); + Base.Rev64(Op[9, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + } +#endif } } diff --git a/Ryujinx.Tests/Cpu/CpuTestAluImm.cs b/Ryujinx.Tests/Cpu/CpuTestAluImm.cs new file mode 100644 index 000000000..5d1f0b6ba --- /dev/null +++ b/Ryujinx.Tests/Cpu/CpuTestAluImm.cs @@ -0,0 +1,811 @@ +//#define AluImm + +using ChocolArm64.State; + +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + using Tester; + using Tester.Types; + + [Category("AluImm"), Ignore("Tested: first half of 2018.")] + public sealed class CpuTestAluImm : CpuTest + { +#if AluImm + [SetUp] + public void SetupTester() + { + AArch64.TakeReset(false); + } + + [Test, Description("ADD , , #{, }")] + public void Add_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(8)] ulong Xn_SP, + [Values(0u, 4095u)] [Random(0u, 4095u, 10)] uint imm, + [Values(0b00u, 0b01u)] uint shift) // + { + uint Opcode = 0x91000000; // ADD X0, X0, #0, LSL #0 + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((shift & 3) << 22) | ((imm & 4095) << 10); + Bits Op = new Bits(Opcode); + + AThreadState ThreadState; + + if (Rn != 31) + { + ThreadState = SingleOpcode(Opcode, X1: Xn_SP); + + AArch64.X((int)Rn, new Bits(Xn_SP)); + } + else + { + ThreadState = SingleOpcode(Opcode, X31: Xn_SP); + + AArch64.SP(new Bits(Xn_SP)); + } + + Base.Add_Imm(Op[31], Op[23, 22], Op[21, 10], Op[9, 5], Op[4, 0]); + + if (Rd != 31) + { + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + ulong SP = AArch64.SP(64).ToUInt64(); + + Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); + } + } + + [Test, Description("ADD , , #{, }")] + public void Add_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(8)] uint Wn_WSP, + [Values(0u, 4095u)] [Random(0u, 4095u, 10)] uint imm, + [Values(0b00u, 0b01u)] uint shift) // + { + uint Opcode = 0x11000000; // ADD W0, W0, #0, LSL #0 + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((shift & 3) << 22) | ((imm & 4095) << 10); + Bits Op = new Bits(Opcode); + + AThreadState ThreadState; + + if (Rn != 31) + { + ThreadState = SingleOpcode(Opcode, X1: Wn_WSP); + + AArch64.X((int)Rn, new Bits(Wn_WSP)); + } + else + { + ThreadState = SingleOpcode(Opcode, X31: Wn_WSP); + + AArch64.SP(new Bits(Wn_WSP)); + } + + Base.Add_Imm(Op[31], Op[23, 22], Op[21, 10], Op[9, 5], Op[4, 0]); + + if (Rd != 31) + { + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + uint WSP = AArch64.SP(32).ToUInt32(); + + Assert.That((uint)ThreadState.X31, Is.EqualTo(WSP)); + } + } + + [Test, Description("ADDS , , #{, }")] + public void Adds_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(8)] ulong Xn_SP, + [Values(0u, 4095u)] [Random(0u, 4095u, 10)] uint imm, + [Values(0b00u, 0b01u)] uint shift) // + { + uint Opcode = 0xB1000000; // ADDS X0, X0, #0, LSL #0 + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((shift & 3) << 22) | ((imm & 4095) << 10); + Bits Op = new Bits(Opcode); + + AThreadState ThreadState; + + if (Rn != 31) + { + ThreadState = SingleOpcode(Opcode, X1: Xn_SP); + + AArch64.X((int)Rn, new Bits(Xn_SP)); + } + else + { + ThreadState = SingleOpcode(Opcode, X31: Xn_SP); + + AArch64.SP(new Bits(Xn_SP)); + } + + Base.Adds_Imm(Op[31], Op[23, 22], Op[21, 10], Op[9, 5], Op[4, 0]); + + if (Rd != 31) + { + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + ulong _X31 = AArch64.SP(64).ToUInt64(); + + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + Assert.Multiple(() => + { + Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); + Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); + Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); + Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); + }); + } + + [Test, Description("ADDS , , #{, }")] + public void Adds_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(8)] uint Wn_WSP, + [Values(0u, 4095u)] [Random(0u, 4095u, 10)] uint imm, + [Values(0b00u, 0b01u)] uint shift) // + { + uint Opcode = 0x31000000; // ADDS W0, W0, #0, LSL #0 + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((shift & 3) << 22) | ((imm & 4095) << 10); + Bits Op = new Bits(Opcode); + + AThreadState ThreadState; + + if (Rn != 31) + { + ThreadState = SingleOpcode(Opcode, X1: Wn_WSP); + + AArch64.X((int)Rn, new Bits(Wn_WSP)); + } + else + { + ThreadState = SingleOpcode(Opcode, X31: Wn_WSP); + + AArch64.SP(new Bits(Wn_WSP)); + } + + Base.Adds_Imm(Op[31], Op[23, 22], Op[21, 10], Op[9, 5], Op[4, 0]); + + if (Rd != 31) + { + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + uint _W31 = AArch64.SP(32).ToUInt32(); + + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + Assert.Multiple(() => + { + Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); + Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); + Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); + Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); + }); + } + + [Test, Description("AND , , #")] + public void And_N1_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(2)] ulong Xn, + [Values(0u, 31u, 32u, 62u)] [Random(0u, 62u, 2)] uint imms, // + [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, 2)] uint immr) // + { + uint Opcode = 0x92400000; // AND X0, X0, #0x1 + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); + Bits Op = new Bits(Opcode); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X31: _X31); + + AArch64.X((int)Rn, new Bits(Xn)); + Base.And_Imm(Op[31], Op[22], Op[21, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + + if (Rd != 31) + { + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + ulong SP = AArch64.SP(64).ToUInt64(); + + Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); + } + } + + [Test, Description("AND , , #")] + public void And_N0_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(2)] ulong Xn, + [Values(0u, 15u, 16u, 30u)] [Random(0u, 30u, 2)] uint imms, // + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 2)] uint immr) // + { + uint Opcode = 0x92000000; // AND X0, X0, #0x100000001 + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); + Bits Op = new Bits(Opcode); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X31: _X31); + + AArch64.X((int)Rn, new Bits(Xn)); + Base.And_Imm(Op[31], Op[22], Op[21, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + + if (Rd != 31) + { + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + ulong SP = AArch64.SP(64).ToUInt64(); + + Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); + } + } + + [Test, Description("AND , , #")] + public void And_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(2)] uint Wn, + [Values(0u, 15u, 16u, 30u)] [Random(0u, 30u, 2)] uint imms, // + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 2)] uint immr) // + { + uint Opcode = 0x12000000; // AND W0, W0, #0x1 + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); + Bits Op = new Bits(Opcode); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X31: _W31); + + AArch64.X((int)Rn, new Bits(Wn)); + Base.And_Imm(Op[31], Op[22], Op[21, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + + if (Rd != 31) + { + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + uint WSP = AArch64.SP(32).ToUInt32(); + + Assert.That((uint)ThreadState.X31, Is.EqualTo(WSP)); + } + } + + [Test, Description("ANDS , , #")] + public void Ands_N1_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(2)] ulong Xn, + [Values(0u, 31u, 32u, 62u)] [Random(0u, 62u, 2)] uint imms, // + [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, 2)] uint immr) // + { + uint Opcode = 0xF2400000; // ANDS X0, X0, #0x1 + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); + Bits Op = new Bits(Opcode); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X31: _X31); + + AArch64.X((int)Rn, new Bits(Xn)); + Base.Ands_Imm(Op[31], Op[22], Op[21, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + if (Rd != 31) + { + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + Assert.Multiple(() => + { + Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); + Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); + Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); + Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); + }); + } + + [Test, Description("ANDS , , #")] + public void Ands_N0_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(2)] ulong Xn, + [Values(0u, 15u, 16u, 30u)] [Random(0u, 30u, 2)] uint imms, // + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 2)] uint immr) // + { + uint Opcode = 0xF2000000; // ANDS X0, X0, #0x100000001 + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); + Bits Op = new Bits(Opcode); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X31: _X31); + + AArch64.X((int)Rn, new Bits(Xn)); + Base.Ands_Imm(Op[31], Op[22], Op[21, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + if (Rd != 31) + { + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + Assert.Multiple(() => + { + Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); + Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); + Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); + Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); + }); + } + + [Test, Description("ANDS , , #")] + public void Ands_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(2)] uint Wn, + [Values(0u, 15u, 16u, 30u)] [Random(0u, 30u, 2)] uint imms, // + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 2)] uint immr) // + { + uint Opcode = 0x72000000; // ANDS W0, W0, #0x1 + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); + Bits Op = new Bits(Opcode); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X31: _W31); + + AArch64.X((int)Rn, new Bits(Wn)); + Base.Ands_Imm(Op[31], Op[22], Op[21, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + if (Rd != 31) + { + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + Assert.Multiple(() => + { + Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); + Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); + Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); + Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); + }); + } + + [Test, Description("EOR , , #")] + public void Eor_N1_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(2)] ulong Xn, + [Values(0u, 31u, 32u, 62u)] [Random(0u, 62u, 2)] uint imms, // + [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, 2)] uint immr) // + { + uint Opcode = 0xD2400000; // EOR X0, X0, #0x1 + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); + Bits Op = new Bits(Opcode); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X31: _X31); + + AArch64.X((int)Rn, new Bits(Xn)); + Base.Eor_Imm(Op[31], Op[22], Op[21, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + + if (Rd != 31) + { + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + ulong SP = AArch64.SP(64).ToUInt64(); + + Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); + } + } + + [Test, Description("EOR , , #")] + public void Eor_N0_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(2)] ulong Xn, + [Values(0u, 15u, 16u, 30u)] [Random(0u, 30u, 2)] uint imms, // + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 2)] uint immr) // + { + uint Opcode = 0xD2000000; // EOR X0, X0, #0x100000001 + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); + Bits Op = new Bits(Opcode); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X31: _X31); + + AArch64.X((int)Rn, new Bits(Xn)); + Base.Eor_Imm(Op[31], Op[22], Op[21, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + + if (Rd != 31) + { + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + ulong SP = AArch64.SP(64).ToUInt64(); + + Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); + } + } + + [Test, Description("EOR , , #")] + public void Eor_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(2)] uint Wn, + [Values(0u, 15u, 16u, 30u)] [Random(0u, 30u, 2)] uint imms, // + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 2)] uint immr) // + { + uint Opcode = 0x52000000; // EOR W0, W0, #0x1 + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); + Bits Op = new Bits(Opcode); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X31: _W31); + + AArch64.X((int)Rn, new Bits(Wn)); + Base.Eor_Imm(Op[31], Op[22], Op[21, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + + if (Rd != 31) + { + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + uint WSP = AArch64.SP(32).ToUInt32(); + + Assert.That((uint)ThreadState.X31, Is.EqualTo(WSP)); + } + } + + [Test, Description("ORR , , #")] + public void Orr_N1_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(2)] ulong Xn, + [Values(0u, 31u, 32u, 62u)] [Random(0u, 62u, 2)] uint imms, // + [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, 2)] uint immr) // + { + uint Opcode = 0xB2400000; // ORR X0, X0, #0x1 + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); + Bits Op = new Bits(Opcode); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X31: _X31); + + AArch64.X((int)Rn, new Bits(Xn)); + Base.Orr_Imm(Op[31], Op[22], Op[21, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + + if (Rd != 31) + { + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + ulong SP = AArch64.SP(64).ToUInt64(); + + Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); + } + } + + [Test, Description("ORR , , #")] + public void Orr_N0_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(2)] ulong Xn, + [Values(0u, 15u, 16u, 30u)] [Random(0u, 30u, 2)] uint imms, // + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 2)] uint immr) // + { + uint Opcode = 0xB2000000; // ORR X0, X0, #0x100000001 + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); + Bits Op = new Bits(Opcode); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X31: _X31); + + AArch64.X((int)Rn, new Bits(Xn)); + Base.Orr_Imm(Op[31], Op[22], Op[21, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + + if (Rd != 31) + { + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + ulong SP = AArch64.SP(64).ToUInt64(); + + Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); + } + } + + [Test, Description("ORR , , #")] + public void Orr_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(2)] uint Wn, + [Values(0u, 15u, 16u, 30u)] [Random(0u, 30u, 2)] uint imms, // + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 2)] uint immr) // + { + uint Opcode = 0x32000000; // ORR W0, W0, #0x1 + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); + Bits Op = new Bits(Opcode); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X31: _W31); + + AArch64.X((int)Rn, new Bits(Wn)); + Base.Orr_Imm(Op[31], Op[22], Op[21, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + + if (Rd != 31) + { + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + uint WSP = AArch64.SP(32).ToUInt32(); + + Assert.That((uint)ThreadState.X31, Is.EqualTo(WSP)); + } + } + + [Test, Description("SUB , , #{, }")] + public void Sub_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(8)] ulong Xn_SP, + [Values(0u, 4095u)] [Random(0u, 4095u, 10)] uint imm, + [Values(0b00u, 0b01u)] uint shift) // + { + uint Opcode = 0xD1000000; // SUB X0, X0, #0, LSL #0 + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((shift & 3) << 22) | ((imm & 4095) << 10); + Bits Op = new Bits(Opcode); + + AThreadState ThreadState; + + if (Rn != 31) + { + ThreadState = SingleOpcode(Opcode, X1: Xn_SP); + + AArch64.X((int)Rn, new Bits(Xn_SP)); + } + else + { + ThreadState = SingleOpcode(Opcode, X31: Xn_SP); + + AArch64.SP(new Bits(Xn_SP)); + } + + Base.Sub_Imm(Op[31], Op[23, 22], Op[21, 10], Op[9, 5], Op[4, 0]); + + if (Rd != 31) + { + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + ulong SP = AArch64.SP(64).ToUInt64(); + + Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); + } + } + + [Test, Description("SUB , , #{, }")] + public void Sub_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(8)] uint Wn_WSP, + [Values(0u, 4095u)] [Random(0u, 4095u, 10)] uint imm, + [Values(0b00u, 0b01u)] uint shift) // + { + uint Opcode = 0x51000000; // SUB W0, W0, #0, LSL #0 + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((shift & 3) << 22) | ((imm & 4095) << 10); + Bits Op = new Bits(Opcode); + + AThreadState ThreadState; + + if (Rn != 31) + { + ThreadState = SingleOpcode(Opcode, X1: Wn_WSP); + + AArch64.X((int)Rn, new Bits(Wn_WSP)); + } + else + { + ThreadState = SingleOpcode(Opcode, X31: Wn_WSP); + + AArch64.SP(new Bits(Wn_WSP)); + } + + Base.Sub_Imm(Op[31], Op[23, 22], Op[21, 10], Op[9, 5], Op[4, 0]); + + if (Rd != 31) + { + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + uint WSP = AArch64.SP(32).ToUInt32(); + + Assert.That((uint)ThreadState.X31, Is.EqualTo(WSP)); + } + } + + [Test, Description("SUBS , , #{, }")] + public void Subs_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(8)] ulong Xn_SP, + [Values(0u, 4095u)] [Random(0u, 4095u, 10)] uint imm, + [Values(0b00u, 0b01u)] uint shift) // + { + uint Opcode = 0xF1000000; // SUBS X0, X0, #0, LSL #0 + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((shift & 3) << 22) | ((imm & 4095) << 10); + Bits Op = new Bits(Opcode); + + AThreadState ThreadState; + + if (Rn != 31) + { + ThreadState = SingleOpcode(Opcode, X1: Xn_SP); + + AArch64.X((int)Rn, new Bits(Xn_SP)); + } + else + { + ThreadState = SingleOpcode(Opcode, X31: Xn_SP); + + AArch64.SP(new Bits(Xn_SP)); + } + + Base.Subs_Imm(Op[31], Op[23, 22], Op[21, 10], Op[9, 5], Op[4, 0]); + + if (Rd != 31) + { + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + ulong _X31 = AArch64.SP(64).ToUInt64(); + + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + Assert.Multiple(() => + { + Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); + Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); + Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); + Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); + }); + } + + [Test, Description("SUBS , , #{, }")] + public void Subs_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(8)] uint Wn_WSP, + [Values(0u, 4095u)] [Random(0u, 4095u, 10)] uint imm, + [Values(0b00u, 0b01u)] uint shift) // + { + uint Opcode = 0x71000000; // SUBS W0, W0, #0, LSL #0 + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((shift & 3) << 22) | ((imm & 4095) << 10); + Bits Op = new Bits(Opcode); + + AThreadState ThreadState; + + if (Rn != 31) + { + ThreadState = SingleOpcode(Opcode, X1: Wn_WSP); + + AArch64.X((int)Rn, new Bits(Wn_WSP)); + } + else + { + ThreadState = SingleOpcode(Opcode, X31: Wn_WSP); + + AArch64.SP(new Bits(Wn_WSP)); + } + + Base.Subs_Imm(Op[31], Op[23, 22], Op[21, 10], Op[9, 5], Op[4, 0]); + + if (Rd != 31) + { + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + uint _W31 = AArch64.SP(32).ToUInt32(); + + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + Assert.Multiple(() => + { + Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); + Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); + Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); + Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); + }); + } +#endif + } +} diff --git a/Ryujinx.Tests/Cpu/CpuTestAluRs.cs b/Ryujinx.Tests/Cpu/CpuTestAluRs.cs new file mode 100644 index 000000000..b81f7100c --- /dev/null +++ b/Ryujinx.Tests/Cpu/CpuTestAluRs.cs @@ -0,0 +1,1911 @@ +//#define AluRs + +using ChocolArm64.State; + +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + using Tester; + using Tester.Types; + + [Category("AluRs"), Ignore("Tested: first half of 2018.")] + public sealed class CpuTestAluRs : CpuTest + { +#if AluRs + [SetUp] + public void SetupTester() + { + AArch64.TakeReset(false); + } + + [Test, Description("ADC , , ")] + public void Adc_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(4)] ulong Xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(4)] ulong Xm, + [Values] bool CarryIn) + { + uint Opcode = 0x9A000000; // ADC X0, X0, X0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31, Carry: CarryIn); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Xn)); + AArch64.X((int)Rm, new Bits(Xm)); + Shared.PSTATE.C = CarryIn; + Base.Adc(Op[31], Op[20, 16], Op[9, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + } + + [Test, Description("ADC , , ")] + public void Adc_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(4)] uint Wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(4)] uint Wm, + [Values] bool CarryIn) + { + uint Opcode = 0x1A000000; // ADC W0, W0, W0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31, Carry: CarryIn); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Wn)); + AArch64.X((int)Rm, new Bits(Wm)); + Shared.PSTATE.C = CarryIn; + Base.Adc(Op[31], Op[20, 16], Op[9, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + } + + [Test, Description("ADCS , , ")] + public void Adcs_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(4)] ulong Xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(4)] ulong Xm, + [Values] bool CarryIn) + { + uint Opcode = 0xBA000000; // ADCS X0, X0, X0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Bits Op = new Bits(Opcode); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31, Carry: CarryIn); + + AArch64.X((int)Rn, new Bits(Xn)); + AArch64.X((int)Rm, new Bits(Xm)); + Shared.PSTATE.C = CarryIn; + Base.Adcs(Op[31], Op[20, 16], Op[9, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + if (Rd != 31) + { + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + Assert.Multiple(() => + { + Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); + Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); + Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); + Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); + }); + } + + [Test, Description("ADCS , , ")] + public void Adcs_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(4)] uint Wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(4)] uint Wm, + [Values] bool CarryIn) + { + uint Opcode = 0x3A000000; // ADCS W0, W0, W0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Bits Op = new Bits(Opcode); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31, Carry: CarryIn); + + AArch64.X((int)Rn, new Bits(Wn)); + AArch64.X((int)Rm, new Bits(Wm)); + Shared.PSTATE.C = CarryIn; + Base.Adcs(Op[31], Op[20, 16], Op[9, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + if (Rd != 31) + { + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + Assert.Multiple(() => + { + Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); + Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); + Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); + Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); + }); + } + + [Test, Description("ADD , , {, #}")] + public void Add_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xm, + [Values(0b00u, 0b01u, 0b10u)] uint shift, // + [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, 1)] uint amount) + { + uint Opcode = 0x8B000000; // ADD X0, X0, X0, LSL #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Xn)); + AArch64.X((int)Rm, new Bits(Xm)); + Base.Add_Rs(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + } + + [Test, Description("ADD , , {, #}")] + public void Add_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wm, + [Values(0b00u, 0b01u, 0b10u)] uint shift, // + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 1)] uint amount) + { + uint Opcode = 0x0B000000; // ADD W0, W0, W0, LSL #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Wn)); + AArch64.X((int)Rm, new Bits(Wm)); + Base.Add_Rs(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + } + + [Test, Description("ADDS , , {, #}")] + public void Adds_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xm, + [Values(0b00u, 0b01u, 0b10u)] uint shift, // + [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, 1)] uint amount) + { + uint Opcode = 0xAB000000; // ADDS X0, X0, X0, LSL #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + Bits Op = new Bits(Opcode); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + + AArch64.X((int)Rn, new Bits(Xn)); + AArch64.X((int)Rm, new Bits(Xm)); + Base.Adds_Rs(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + if (Rd != 31) + { + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + Assert.Multiple(() => + { + Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); + Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); + Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); + Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); + }); + } + + [Test, Description("ADDS , , {, #}")] + public void Adds_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wm, + [Values(0b00u, 0b01u, 0b10u)] uint shift, // + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 1)] uint amount) + { + uint Opcode = 0x2B000000; // ADDS W0, W0, W0, LSL #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + Bits Op = new Bits(Opcode); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + + AArch64.X((int)Rn, new Bits(Wn)); + AArch64.X((int)Rm, new Bits(Wm)); + Base.Adds_Rs(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + if (Rd != 31) + { + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + Assert.Multiple(() => + { + Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); + Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); + Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); + Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); + }); + } + + [Test, Description("AND , , {, #}")] + public void And_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xm, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // + [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, 1)] uint amount) + { + uint Opcode = 0x8A000000; // AND X0, X0, X0, LSL #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Xn)); + AArch64.X((int)Rm, new Bits(Xm)); + Base.And_Rs(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + } + + [Test, Description("AND , , {, #}")] + public void And_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wm, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 1)] uint amount) + { + uint Opcode = 0x0A000000; // AND W0, W0, W0, LSL #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Wn)); + AArch64.X((int)Rm, new Bits(Wm)); + Base.And_Rs(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + } + + [Test, Description("ANDS , , {, #}")] + public void Ands_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xm, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // + [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, 1)] uint amount) + { + uint Opcode = 0xEA000000; // ANDS X0, X0, X0, LSL #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + Bits Op = new Bits(Opcode); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + + AArch64.X((int)Rn, new Bits(Xn)); + AArch64.X((int)Rm, new Bits(Xm)); + Base.Ands_Rs(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + if (Rd != 31) + { + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + Assert.Multiple(() => + { + Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); + Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); + Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); + Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); + }); + } + + [Test, Description("ANDS , , {, #}")] + public void Ands_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wm, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 1)] uint amount) + { + uint Opcode = 0x6A000000; // ANDS W0, W0, W0, LSL #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + Bits Op = new Bits(Opcode); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + + AArch64.X((int)Rn, new Bits(Wn)); + AArch64.X((int)Rm, new Bits(Wm)); + Base.Ands_Rs(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + if (Rd != 31) + { + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + Assert.Multiple(() => + { + Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); + Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); + Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); + Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); + }); + } + + [Test, Description("ASRV , , ")] + public void Asrv_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(8)] ulong Xn, + [Values(0ul, 31ul, 32ul, 63ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(5)] ulong Xm) + { + uint Opcode = 0x9AC02800; // ASRV X0, X0, X0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Xn)); + AArch64.X((int)Rm, new Bits(Xm)); + Base.Asrv(Op[31], Op[20, 16], Op[9, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + } + + [Test, Description("ASRV , , ")] + public void Asrv_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(8)] uint Wn, + [Values(0u, 15u, 16u, 31u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(5)] uint Wm) + { + uint Opcode = 0x1AC02800; // ASRV W0, W0, W0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Wn)); + AArch64.X((int)Rm, new Bits(Wm)); + Base.Asrv(Op[31], Op[20, 16], Op[9, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + } + + [Test, Description("BIC , , {, #}")] + public void Bic_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xm, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // + [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, 1)] uint amount) + { + uint Opcode = 0x8A200000; // BIC X0, X0, X0, LSL #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Xn)); + AArch64.X((int)Rm, new Bits(Xm)); + Base.Bic(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + } + + [Test, Description("BIC , , {, #}")] + public void Bic_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wm, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 1)] uint amount) + { + uint Opcode = 0x0A200000; // BIC W0, W0, W0, LSL #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Wn)); + AArch64.X((int)Rm, new Bits(Wm)); + Base.Bic(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + } + + [Test, Description("BICS , , {, #}")] + public void Bics_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xm, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // + [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, 1)] uint amount) + { + uint Opcode = 0xEA200000; // BICS X0, X0, X0, LSL #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + Bits Op = new Bits(Opcode); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + + AArch64.X((int)Rn, new Bits(Xn)); + AArch64.X((int)Rm, new Bits(Xm)); + Base.Bics(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + if (Rd != 31) + { + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + Assert.Multiple(() => + { + Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); + Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); + Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); + Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); + }); + } + + [Test, Description("BICS , , {, #}")] + public void Bics_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wm, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 1)] uint amount) + { + uint Opcode = 0x6A200000; // BICS W0, W0, W0, LSL #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + Bits Op = new Bits(Opcode); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + + AArch64.X((int)Rn, new Bits(Wn)); + AArch64.X((int)Rm, new Bits(Wm)); + Base.Bics(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + if (Rd != 31) + { + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + Assert.Multiple(() => + { + Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); + Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); + Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); + Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); + }); + } + + [Test, Description("CRC32X , , ")] + public void Crc32x([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0xFFFFFFFFu)] [Random(2)] uint Wn, + [Values((ulong)0x00_00_00_00_00_00_00_00, + (ulong)0x7F_FF_FF_FF_FF_FF_FF_FF, + (ulong)0x80_00_00_00_00_00_00_00, + (ulong)0xFF_FF_FF_FF_FF_FF_FF_FF)] [Random(64)] ulong Xm) + { + uint Opcode = 0x9AC04C00; // CRC32X W0, W0, X0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Xm, X31: _W31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Wn)); + AArch64.X((int)Rm, new Bits(Xm)); + Base.Crc32(Op[31], Op[20, 16], Op[11, 10], Op[9, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + } + + [Test, Description("CRC32W , , ")] + public void Crc32w([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0xFFFFFFFFu)] [Random(2)] uint Wn, + [Values((uint)0x00_00_00_00, (uint)0x7F_FF_FF_FF, + (uint)0x80_00_00_00, (uint)0xFF_FF_FF_FF)] [Random(64)] uint Wm) + { + uint Opcode = 0x1AC04800; // CRC32W W0, W0, W0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Wn)); + AArch64.X((int)Rm, new Bits(Wm)); + Base.Crc32(Op[31], Op[20, 16], Op[11, 10], Op[9, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + } + + [Test, Description("CRC32H , , ")] + public void Crc32h([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0xFFFFFFFFu)] [Random(2)] uint Wn, + [Values((ushort)0x00_00, (ushort)0x7F_FF, + (ushort)0x80_00, (ushort)0xFF_FF)] [Random(64)] ushort Wm) + { + uint Opcode = 0x1AC04400; // CRC32H W0, W0, W0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Wn)); + AArch64.X((int)Rm, new Bits(Wm)); + Base.Crc32(Op[31], Op[20, 16], Op[11, 10], Op[9, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + } + + [Test, Description("CRC32B , , ")] + public void Crc32b([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0xFFFFFFFFu)] [Random(2)] uint Wn, + [Values((byte)0x00, (byte)0x7F, + (byte)0x80, (byte)0xFF)] [Random(64)] byte Wm) + { + uint Opcode = 0x1AC04000; // CRC32B W0, W0, W0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Wn)); + AArch64.X((int)Rm, new Bits(Wm)); + Base.Crc32(Op[31], Op[20, 16], Op[11, 10], Op[9, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + } + + [Test, Description("CRC32CX , , ")] + public void Crc32cx([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0xFFFFFFFFu)] [Random(2)] uint Wn, + [Values((ulong)0x00_00_00_00_00_00_00_00, + (ulong)0x7F_FF_FF_FF_FF_FF_FF_FF, + (ulong)0x80_00_00_00_00_00_00_00, + (ulong)0xFF_FF_FF_FF_FF_FF_FF_FF)] [Random(64)] ulong Xm) + { + uint Opcode = 0x9AC05C00; // CRC32CX W0, W0, X0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Xm, X31: _W31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Wn)); + AArch64.X((int)Rm, new Bits(Xm)); + Base.Crc32c(Op[31], Op[20, 16], Op[11, 10], Op[9, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + } + + [Test, Description("CRC32CW , , ")] + public void Crc32cw([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0xFFFFFFFFu)] [Random(2)] uint Wn, + [Values((uint)0x00_00_00_00, (uint)0x7F_FF_FF_FF, + (uint)0x80_00_00_00, (uint)0xFF_FF_FF_FF)] [Random(64)] uint Wm) + { + uint Opcode = 0x1AC05800; // CRC32CW W0, W0, W0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Wn)); + AArch64.X((int)Rm, new Bits(Wm)); + Base.Crc32c(Op[31], Op[20, 16], Op[11, 10], Op[9, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + } + + [Test, Description("CRC32CH , , ")] + public void Crc32ch([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0xFFFFFFFFu)] [Random(2)] uint Wn, + [Values((ushort)0x00_00, (ushort)0x7F_FF, + (ushort)0x80_00, (ushort)0xFF_FF)] [Random(64)] ushort Wm) + { + uint Opcode = 0x1AC05400; // CRC32CH W0, W0, W0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Wn)); + AArch64.X((int)Rm, new Bits(Wm)); + Base.Crc32c(Op[31], Op[20, 16], Op[11, 10], Op[9, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + } + + [Test, Description("CRC32CB , , ")] + public void Crc32cb([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0xFFFFFFFFu)] [Random(2)] uint Wn, + [Values((byte)0x00, (byte)0x7F, + (byte)0x80, (byte)0xFF)] [Random(64)] byte Wm) + { + uint Opcode = 0x1AC05000; // CRC32CB W0, W0, W0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Wn)); + AArch64.X((int)Rm, new Bits(Wm)); + Base.Crc32c(Op[31], Op[20, 16], Op[11, 10], Op[9, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + } + + [Test, Description("EON , , {, #}")] + public void Eon_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xm, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // + [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, 1)] uint amount) + { + uint Opcode = 0xCA200000; // EON X0, X0, X0, LSL #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Xn)); + AArch64.X((int)Rm, new Bits(Xm)); + Base.Eon(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + } + + [Test, Description("EON , , {, #}")] + public void Eon_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wm, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 1)] uint amount) + { + uint Opcode = 0x4A200000; // EON W0, W0, W0, LSL #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Wn)); + AArch64.X((int)Rm, new Bits(Wm)); + Base.Eon(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + } + + [Test, Description("EOR , , {, #}")] + public void Eor_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xm, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // + [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, 1)] uint amount) + { + uint Opcode = 0xCA000000; // EOR X0, X0, X0, LSL #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Xn)); + AArch64.X((int)Rm, new Bits(Xm)); + Base.Eor_Rs(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + } + + [Test, Description("EOR , , {, #}")] + public void Eor_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wm, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 1)] uint amount) + { + uint Opcode = 0x4A000000; // EOR W0, W0, W0, LSL #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Wn)); + AArch64.X((int)Rm, new Bits(Wm)); + Base.Eor_Rs(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + } + + [Test, Description("EXTR , , , #")] + public void Extr_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(2)] ulong Xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(2)] ulong Xm, + [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, 2)] uint lsb) + { + uint Opcode = 0x93C00000; // EXTR X0, X0, X0, #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((lsb & 63) << 10); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Xn)); + AArch64.X((int)Rm, new Bits(Xm)); + Base.Extr(Op[31], Op[22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + } + + [Test, Description("EXTR , , , #")] + public void Extr_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(2)] uint Wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(2)] uint Wm, + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 2)] uint lsb) + { + uint Opcode = 0x13800000; // EXTR W0, W0, W0, #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((lsb & 63) << 10); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Wn)); + AArch64.X((int)Rm, new Bits(Wm)); + Base.Extr(Op[31], Op[22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + } + + [Test, Description("LSLV , , ")] + public void Lslv_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(8)] ulong Xn, + [Values(0ul, 31ul, 32ul, 63ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(5)] ulong Xm) + { + uint Opcode = 0x9AC02000; // LSLV X0, X0, X0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Xn)); + AArch64.X((int)Rm, new Bits(Xm)); + Base.Lslv(Op[31], Op[20, 16], Op[9, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + } + + [Test, Description("LSLV , , ")] + public void Lslv_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(8)] uint Wn, + [Values(0u, 15u, 16u, 31u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(5)] uint Wm) + { + uint Opcode = 0x1AC02000; // LSLV W0, W0, W0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Wn)); + AArch64.X((int)Rm, new Bits(Wm)); + Base.Lslv(Op[31], Op[20, 16], Op[9, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + } + + [Test, Description("LSRV , , ")] + public void Lsrv_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(8)] ulong Xn, + [Values(0ul, 31ul, 32ul, 63ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(5)] ulong Xm) + { + uint Opcode = 0x9AC02400; // LSRV X0, X0, X0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Xn)); + AArch64.X((int)Rm, new Bits(Xm)); + Base.Lsrv(Op[31], Op[20, 16], Op[9, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + } + + [Test, Description("LSRV , , ")] + public void Lsrv_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(8)] uint Wn, + [Values(0u, 15u, 16u, 31u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(5)] uint Wm) + { + uint Opcode = 0x1AC02400; // LSRV W0, W0, W0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Wn)); + AArch64.X((int)Rm, new Bits(Wm)); + Base.Lsrv(Op[31], Op[20, 16], Op[9, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + } + + [Test, Description("ORN , , {, #}")] + public void Orn_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xm, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // + [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, 1)] uint amount) + { + uint Opcode = 0xAA200000; // ORN X0, X0, X0, LSL #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Xn)); + AArch64.X((int)Rm, new Bits(Xm)); + Base.Orn(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + } + + [Test, Description("ORN , , {, #}")] + public void Orn_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wm, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 1)] uint amount) + { + uint Opcode = 0x2A200000; // ORN W0, W0, W0, LSL #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Wn)); + AArch64.X((int)Rm, new Bits(Wm)); + Base.Orn(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + } + + [Test, Description("ORR , , {, #}")] + public void Orr_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xm, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // + [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, 1)] uint amount) + { + uint Opcode = 0xAA000000; // ORR X0, X0, X0, LSL #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Xn)); + AArch64.X((int)Rm, new Bits(Xm)); + Base.Orr_Rs(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + } + + [Test, Description("ORR , , {, #}")] + public void Orr_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wm, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 1)] uint amount) + { + uint Opcode = 0x2A000000; // ORR W0, W0, W0, LSL #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Wn)); + AArch64.X((int)Rm, new Bits(Wm)); + Base.Orr_Rs(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + } + + [Test, Description("RORV , , ")] + public void Rorv_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(8)] ulong Xn, + [Values(0ul, 31ul, 32ul, 63ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(5)] ulong Xm) + { + uint Opcode = 0x9AC02C00; // RORV X0, X0, X0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Xn)); + AArch64.X((int)Rm, new Bits(Xm)); + Base.Rorv(Op[31], Op[20, 16], Op[9, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + } + + [Test, Description("RORV , , ")] + public void Rorv_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(8)] uint Wn, + [Values(0u, 15u, 16u, 31u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(5)] uint Wm) + { + uint Opcode = 0x1AC02C00; // RORV W0, W0, W0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Wn)); + AArch64.X((int)Rm, new Bits(Wm)); + Base.Rorv(Op[31], Op[20, 16], Op[9, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + } + + [Test, Description("SBC , , ")] + public void Sbc_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(4)] ulong Xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(4)] ulong Xm, + [Values] bool CarryIn) + { + uint Opcode = 0xDA000000; // SBC X0, X0, X0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31, Carry: CarryIn); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Xn)); + AArch64.X((int)Rm, new Bits(Xm)); + Shared.PSTATE.C = CarryIn; + Base.Sbc(Op[31], Op[20, 16], Op[9, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + } + + [Test, Description("SBC , , ")] + public void Sbc_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(4)] uint Wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(4)] uint Wm, + [Values] bool CarryIn) + { + uint Opcode = 0x5A000000; // SBC W0, W0, W0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31, Carry: CarryIn); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Wn)); + AArch64.X((int)Rm, new Bits(Wm)); + Shared.PSTATE.C = CarryIn; + Base.Sbc(Op[31], Op[20, 16], Op[9, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + } + + [Test, Description("SBCS , , ")] + public void Sbcs_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(4)] ulong Xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(4)] ulong Xm, + [Values] bool CarryIn) + { + uint Opcode = 0xFA000000; // SBCS X0, X0, X0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Bits Op = new Bits(Opcode); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31, Carry: CarryIn); + + AArch64.X((int)Rn, new Bits(Xn)); + AArch64.X((int)Rm, new Bits(Xm)); + Shared.PSTATE.C = CarryIn; + Base.Sbcs(Op[31], Op[20, 16], Op[9, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + if (Rd != 31) + { + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + Assert.Multiple(() => + { + Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); + Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); + Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); + Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); + }); + } + + [Test, Description("SBCS , , ")] + public void Sbcs_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(4)] uint Wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(4)] uint Wm, + [Values] bool CarryIn) + { + uint Opcode = 0x7A000000; // SBCS W0, W0, W0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Bits Op = new Bits(Opcode); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31, Carry: CarryIn); + + AArch64.X((int)Rn, new Bits(Wn)); + AArch64.X((int)Rm, new Bits(Wm)); + Shared.PSTATE.C = CarryIn; + Base.Sbcs(Op[31], Op[20, 16], Op[9, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + if (Rd != 31) + { + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + Assert.Multiple(() => + { + Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); + Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); + Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); + Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); + }); + } + + [Test, Description("SDIV , , ")] + public void Sdiv_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(8)] ulong Xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(8)] ulong Xm) + { + uint Opcode = 0x9AC00C00; // SDIV X0, X0, X0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Xn)); + AArch64.X((int)Rm, new Bits(Xm)); + Base.Sdiv(Op[31], Op[20, 16], Op[9, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + } + + [Test, Description("SDIV , , ")] + public void Sdiv_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(8)] uint Wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(8)] uint Wm) + { + uint Opcode = 0x1AC00C00; // SDIV W0, W0, W0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Wn)); + AArch64.X((int)Rm, new Bits(Wm)); + Base.Sdiv(Op[31], Op[20, 16], Op[9, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + } + + [Test, Description("SUB , , {, #}")] + public void Sub_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xm, + [Values(0b00u, 0b01u, 0b10u)] uint shift, // + [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, 1)] uint amount) + { + uint Opcode = 0xCB000000; // SUB X0, X0, X0, LSL #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Xn)); + AArch64.X((int)Rm, new Bits(Xm)); + Base.Sub_Rs(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + } + + [Test, Description("SUB , , {, #}")] + public void Sub_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wm, + [Values(0b00u, 0b01u, 0b10u)] uint shift, // + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 1)] uint amount) + { + uint Opcode = 0x4B000000; // SUB W0, W0, W0, LSL #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Wn)); + AArch64.X((int)Rm, new Bits(Wm)); + Base.Sub_Rs(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + } + + [Test, Description("SUBS , , {, #}")] + public void Subs_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xm, + [Values(0b00u, 0b01u, 0b10u)] uint shift, // + [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, 1)] uint amount) + { + uint Opcode = 0xEB000000; // SUBS X0, X0, X0, LSL #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + Bits Op = new Bits(Opcode); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + + AArch64.X((int)Rn, new Bits(Xn)); + AArch64.X((int)Rm, new Bits(Xm)); + Base.Subs_Rs(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + if (Rd != 31) + { + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + Assert.Multiple(() => + { + Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); + Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); + Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); + Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); + }); + } + + [Test, Description("SUBS , , {, #}")] + public void Subs_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wm, + [Values(0b00u, 0b01u, 0b10u)] uint shift, // + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 1)] uint amount) + { + uint Opcode = 0x6B000000; // SUBS W0, W0, W0, LSL #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + Bits Op = new Bits(Opcode); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + + AArch64.X((int)Rn, new Bits(Wn)); + AArch64.X((int)Rm, new Bits(Wm)); + Base.Subs_Rs(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + if (Rd != 31) + { + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + Assert.Multiple(() => + { + Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); + Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); + Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); + Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); + }); + } + + [Test, Description("UDIV , , ")] + public void Udiv_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(8)] ulong Xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(8)] ulong Xm) + { + uint Opcode = 0x9AC00800; // UDIV X0, X0, X0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Xn)); + AArch64.X((int)Rm, new Bits(Xm)); + Base.Udiv(Op[31], Op[20, 16], Op[9, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + } + + [Test, Description("UDIV , , ")] + public void Udiv_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(8)] uint Wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(8)] uint Wm) + { + uint Opcode = 0x1AC00800; // UDIV W0, W0, W0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Wn)); + AArch64.X((int)Rm, new Bits(Wm)); + Base.Udiv(Op[31], Op[20, 16], Op[9, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + } +#endif + } +} diff --git a/Ryujinx.Tests/Cpu/CpuTestAluRx.cs b/Ryujinx.Tests/Cpu/CpuTestAluRx.cs new file mode 100644 index 000000000..26169bca6 --- /dev/null +++ b/Ryujinx.Tests/Cpu/CpuTestAluRx.cs @@ -0,0 +1,1349 @@ +//#define AluRx + +using ChocolArm64.State; + +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + using Tester; + using Tester.Types; + + [Category("AluRx"), Ignore("Tested: first half of 2018.")] + public sealed class CpuTestAluRx : CpuTest + { +#if AluRx + [SetUp] + public void SetupTester() + { + AArch64.TakeReset(false); + } + + [Test, Description("ADD , , {, {#}}")] + public void Add_X_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn_SP, + [Values((ulong)0x0000000000000000, (ulong)0x7FFFFFFFFFFFFFFF, + (ulong)0x8000000000000000, (ulong)0xFFFFFFFFFFFFFFFF)] [Random(2)] ulong Xm, + [Values(0b011u, 0b111u)] uint extend, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint Opcode = 0x8B206000; // ADD X0, X0, X0, UXTX #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + Bits Op = new Bits(Opcode); + + AThreadState ThreadState; + + if (Rn != 31) + { + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + ThreadState = SingleOpcode(Opcode, X1: Xn_SP, X2: Xm, X31: _X31); + + AArch64.X((int)Rn, new Bits(Xn_SP)); + } + else + { + ThreadState = SingleOpcode(Opcode, X31: Xn_SP, X2: Xm); + + AArch64.SP(new Bits(Xn_SP)); + } + + AArch64.X((int)Rm, new Bits(Xm)); + Base.Add_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); + + if (Rd != 31) + { + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + ulong SP = AArch64.SP(64).ToUInt64(); + + Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); + } + } + + [Test, Description("ADD , , {, {#}}")] + public void Add_W_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn_SP, + [Values((uint)0x00000000, (uint)0x7FFFFFFF, + (uint)0x80000000, (uint)0xFFFFFFFF)] [Random(2)] uint Wm, + [Values(0b000u, 0b001u, 0b010u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint Opcode = 0x8B200000; // ADD X0, X0, W0, UXTB #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + Bits Op = new Bits(Opcode); + + AThreadState ThreadState; + + if (Rn != 31) + { + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + ThreadState = SingleOpcode(Opcode, X1: Xn_SP, X2: Wm, X31: _X31); + + AArch64.X((int)Rn, new Bits(Xn_SP)); + } + else + { + ThreadState = SingleOpcode(Opcode, X31: Xn_SP, X2: Wm); + + AArch64.SP(new Bits(Xn_SP)); + } + + AArch64.X((int)Rm, new Bits(Wm)); + Base.Add_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); + + if (Rd != 31) + { + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + ulong SP = AArch64.SP(64).ToUInt64(); + + Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); + } + } + + [Test, Description("ADD , , {, {#}}")] + public void Add_H_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn_SP, + [Values((ushort)0x0000, (ushort)0x7FFF, + (ushort)0x8000, (ushort)0xFFFF)] [Random(2)] ushort Wm, + [Values(0b000u, 0b001u, 0b010u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint Opcode = 0x8B200000; // ADD X0, X0, W0, UXTB #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + Bits Op = new Bits(Opcode); + + AThreadState ThreadState; + + if (Rn != 31) + { + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + ThreadState = SingleOpcode(Opcode, X1: Xn_SP, X2: Wm, X31: _X31); + + AArch64.X((int)Rn, new Bits(Xn_SP)); + } + else + { + ThreadState = SingleOpcode(Opcode, X31: Xn_SP, X2: Wm); + + AArch64.SP(new Bits(Xn_SP)); + } + + AArch64.X((int)Rm, new Bits(Wm)); + Base.Add_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); + + if (Rd != 31) + { + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + ulong SP = AArch64.SP(64).ToUInt64(); + + Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); + } + } + + [Test, Description("ADD , , {, {#}}")] + public void Add_B_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn_SP, + [Values((byte)0x00, (byte)0x7F, + (byte)0x80, (byte)0xFF)] [Random(2)] byte Wm, + [Values(0b000u, 0b001u, 0b010u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint Opcode = 0x8B200000; // ADD X0, X0, W0, UXTB #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + Bits Op = new Bits(Opcode); + + AThreadState ThreadState; + + if (Rn != 31) + { + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + ThreadState = SingleOpcode(Opcode, X1: Xn_SP, X2: Wm, X31: _X31); + + AArch64.X((int)Rn, new Bits(Xn_SP)); + } + else + { + ThreadState = SingleOpcode(Opcode, X31: Xn_SP, X2: Wm); + + AArch64.SP(new Bits(Xn_SP)); + } + + AArch64.X((int)Rm, new Bits(Wm)); + Base.Add_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); + + if (Rd != 31) + { + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + ulong SP = AArch64.SP(64).ToUInt64(); + + Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); + } + } + + [Test, Description("ADD , , {, {#}}")] + public void Add_W_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn_WSP, + [Values((uint)0x00000000, (uint)0x7FFFFFFF, + (uint)0x80000000, (uint)0xFFFFFFFF)] [Random(2)] uint Wm, + [Values(0b000u, 0b001u, 0b010u, 0b011u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint Opcode = 0x0B200000; // ADD W0, W0, W0, UXTB #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + Bits Op = new Bits(Opcode); + + AThreadState ThreadState; + + if (Rn != 31) + { + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + ThreadState = SingleOpcode(Opcode, X1: Wn_WSP, X2: Wm, X31: _W31); + + AArch64.X((int)Rn, new Bits(Wn_WSP)); + } + else + { + ThreadState = SingleOpcode(Opcode, X31: Wn_WSP, X2: Wm); + + AArch64.SP(new Bits(Wn_WSP)); + } + + AArch64.X((int)Rm, new Bits(Wm)); + Base.Add_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); + + if (Rd != 31) + { + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + uint WSP = AArch64.SP(32).ToUInt32(); + + Assert.That((uint)ThreadState.X31, Is.EqualTo(WSP)); + } + } + + [Test, Description("ADD , , {, {#}}")] + public void Add_H_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn_WSP, + [Values((ushort)0x0000, (ushort)0x7FFF, + (ushort)0x8000, (ushort)0xFFFF)] [Random(2)] ushort Wm, + [Values(0b000u, 0b001u, 0b010u, 0b011u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint Opcode = 0x0B200000; // ADD W0, W0, W0, UXTB #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + Bits Op = new Bits(Opcode); + + AThreadState ThreadState; + + if (Rn != 31) + { + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + ThreadState = SingleOpcode(Opcode, X1: Wn_WSP, X2: Wm, X31: _W31); + + AArch64.X((int)Rn, new Bits(Wn_WSP)); + } + else + { + ThreadState = SingleOpcode(Opcode, X31: Wn_WSP, X2: Wm); + + AArch64.SP(new Bits(Wn_WSP)); + } + + AArch64.X((int)Rm, new Bits(Wm)); + Base.Add_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); + + if (Rd != 31) + { + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + uint WSP = AArch64.SP(32).ToUInt32(); + + Assert.That((uint)ThreadState.X31, Is.EqualTo(WSP)); + } + } + + [Test, Description("ADD , , {, {#}}")] + public void Add_B_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn_WSP, + [Values((byte)0x00, (byte)0x7F, + (byte)0x80, (byte)0xFF)] [Random(2)] byte Wm, + [Values(0b000u, 0b001u, 0b010u, 0b011u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint Opcode = 0x0B200000; // ADD W0, W0, W0, UXTB #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + Bits Op = new Bits(Opcode); + + AThreadState ThreadState; + + if (Rn != 31) + { + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + ThreadState = SingleOpcode(Opcode, X1: Wn_WSP, X2: Wm, X31: _W31); + + AArch64.X((int)Rn, new Bits(Wn_WSP)); + } + else + { + ThreadState = SingleOpcode(Opcode, X31: Wn_WSP, X2: Wm); + + AArch64.SP(new Bits(Wn_WSP)); + } + + AArch64.X((int)Rm, new Bits(Wm)); + Base.Add_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); + + if (Rd != 31) + { + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + uint WSP = AArch64.SP(32).ToUInt32(); + + Assert.That((uint)ThreadState.X31, Is.EqualTo(WSP)); + } + } + + [Test, Description("ADDS , , {, {#}}")] + public void Adds_X_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn_SP, + [Values((ulong)0x0000000000000000, (ulong)0x7FFFFFFFFFFFFFFF, + (ulong)0x8000000000000000, (ulong)0xFFFFFFFFFFFFFFFF)] [Random(2)] ulong Xm, + [Values(0b011u, 0b111u)] uint extend, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint Opcode = 0xAB206000; // ADDS X0, X0, X0, UXTX #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + Bits Op = new Bits(Opcode); + + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn_SP, X2: Xm, X31: Xn_SP); + + AArch64.X((int)Rn, new Bits(Xn_SP)); + AArch64.X((int)Rm, new Bits(Xm)); + AArch64.SP(new Bits(Xn_SP)); + Base.Adds_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); + + if (Rd != 31) + { + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + ulong _X31 = AArch64.SP(64).ToUInt64(); + + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + Assert.Multiple(() => + { + Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); + Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); + Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); + Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); + }); + } + + [Test, Description("ADDS , , {, {#}}")] + public void Adds_W_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn_SP, + [Values((uint)0x00000000, (uint)0x7FFFFFFF, + (uint)0x80000000, (uint)0xFFFFFFFF)] [Random(2)] uint Wm, + [Values(0b000u, 0b001u, 0b010u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint Opcode = 0xAB200000; // ADDS X0, X0, W0, UXTB #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + Bits Op = new Bits(Opcode); + + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn_SP, X2: Wm, X31: Xn_SP); + + AArch64.X((int)Rn, new Bits(Xn_SP)); + AArch64.X((int)Rm, new Bits(Wm)); + AArch64.SP(new Bits(Xn_SP)); + Base.Adds_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); + + if (Rd != 31) + { + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + ulong _X31 = AArch64.SP(64).ToUInt64(); + + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + Assert.Multiple(() => + { + Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); + Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); + Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); + Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); + }); + } + + [Test, Description("ADDS , , {, {#}}")] + public void Adds_H_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn_SP, + [Values((ushort)0x0000, (ushort)0x7FFF, + (ushort)0x8000, (ushort)0xFFFF)] [Random(2)] ushort Wm, + [Values(0b000u, 0b001u, 0b010u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint Opcode = 0xAB200000; // ADDS X0, X0, W0, UXTB #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + Bits Op = new Bits(Opcode); + + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn_SP, X2: Wm, X31: Xn_SP); + + AArch64.X((int)Rn, new Bits(Xn_SP)); + AArch64.X((int)Rm, new Bits(Wm)); + AArch64.SP(new Bits(Xn_SP)); + Base.Adds_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); + + if (Rd != 31) + { + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + ulong _X31 = AArch64.SP(64).ToUInt64(); + + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + Assert.Multiple(() => + { + Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); + Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); + Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); + Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); + }); + } + + [Test, Description("ADDS , , {, {#}}")] + public void Adds_B_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn_SP, + [Values((byte)0x00, (byte)0x7F, + (byte)0x80, (byte)0xFF)] [Random(2)] byte Wm, + [Values(0b000u, 0b001u, 0b010u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint Opcode = 0xAB200000; // ADDS X0, X0, W0, UXTB #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + Bits Op = new Bits(Opcode); + + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn_SP, X2: Wm, X31: Xn_SP); + + AArch64.X((int)Rn, new Bits(Xn_SP)); + AArch64.X((int)Rm, new Bits(Wm)); + AArch64.SP(new Bits(Xn_SP)); + Base.Adds_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); + + if (Rd != 31) + { + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + ulong _X31 = AArch64.SP(64).ToUInt64(); + + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + Assert.Multiple(() => + { + Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); + Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); + Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); + Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); + }); + } + + [Test, Description("ADDS , , {, {#}}")] + public void Adds_W_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn_WSP, + [Values((uint)0x00000000, (uint)0x7FFFFFFF, + (uint)0x80000000, (uint)0xFFFFFFFF)] [Random(2)] uint Wm, + [Values(0b000u, 0b001u, 0b010u, 0b011u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint Opcode = 0x2B200000; // ADDS W0, W0, W0, UXTB #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + Bits Op = new Bits(Opcode); + + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn_WSP, X2: Wm, X31: Wn_WSP); + + AArch64.X((int)Rn, new Bits(Wn_WSP)); + AArch64.X((int)Rm, new Bits(Wm)); + AArch64.SP(new Bits(Wn_WSP)); + Base.Adds_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); + + if (Rd != 31) + { + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + uint _W31 = AArch64.SP(32).ToUInt32(); + + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + Assert.Multiple(() => + { + Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); + Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); + Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); + Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); + }); + } + + [Test, Description("ADDS , , {, {#}}")] + public void Adds_H_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn_WSP, + [Values((ushort)0x0000, (ushort)0x7FFF, + (ushort)0x8000, (ushort)0xFFFF)] [Random(2)] ushort Wm, + [Values(0b000u, 0b001u, 0b010u, 0b011u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint Opcode = 0x2B200000; // ADDS W0, W0, W0, UXTB #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + Bits Op = new Bits(Opcode); + + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn_WSP, X2: Wm, X31: Wn_WSP); + + AArch64.X((int)Rn, new Bits(Wn_WSP)); + AArch64.X((int)Rm, new Bits(Wm)); + AArch64.SP(new Bits(Wn_WSP)); + Base.Adds_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); + + if (Rd != 31) + { + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + uint _W31 = AArch64.SP(32).ToUInt32(); + + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + Assert.Multiple(() => + { + Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); + Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); + Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); + Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); + }); + } + + [Test, Description("ADDS , , {, {#}}")] + public void Adds_B_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn_WSP, + [Values((byte)0x00, (byte)0x7F, + (byte)0x80, (byte)0xFF)] [Random(2)] byte Wm, + [Values(0b000u, 0b001u, 0b010u, 0b011u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint Opcode = 0x2B200000; // ADDS W0, W0, W0, UXTB #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + Bits Op = new Bits(Opcode); + + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn_WSP, X2: Wm, X31: Wn_WSP); + + AArch64.X((int)Rn, new Bits(Wn_WSP)); + AArch64.X((int)Rm, new Bits(Wm)); + AArch64.SP(new Bits(Wn_WSP)); + Base.Adds_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); + + if (Rd != 31) + { + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + uint _W31 = AArch64.SP(32).ToUInt32(); + + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + Assert.Multiple(() => + { + Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); + Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); + Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); + Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); + }); + } + + [Test, Description("SUB , , {, {#}}")] + public void Sub_X_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn_SP, + [Values((ulong)0x0000000000000000, (ulong)0x7FFFFFFFFFFFFFFF, + (ulong)0x8000000000000000, (ulong)0xFFFFFFFFFFFFFFFF)] [Random(2)] ulong Xm, + [Values(0b011u, 0b111u)] uint extend, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint Opcode = 0xCB206000; // SUB X0, X0, X0, UXTX #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + Bits Op = new Bits(Opcode); + + AThreadState ThreadState; + + if (Rn != 31) + { + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + ThreadState = SingleOpcode(Opcode, X1: Xn_SP, X2: Xm, X31: _X31); + + AArch64.X((int)Rn, new Bits(Xn_SP)); + } + else + { + ThreadState = SingleOpcode(Opcode, X31: Xn_SP, X2: Xm); + + AArch64.SP(new Bits(Xn_SP)); + } + + AArch64.X((int)Rm, new Bits(Xm)); + Base.Sub_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); + + if (Rd != 31) + { + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + ulong SP = AArch64.SP(64).ToUInt64(); + + Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); + } + } + + [Test, Description("SUB , , {, {#}}")] + public void Sub_W_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn_SP, + [Values((uint)0x00000000, (uint)0x7FFFFFFF, + (uint)0x80000000, (uint)0xFFFFFFFF)] [Random(2)] uint Wm, + [Values(0b000u, 0b001u, 0b010u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint Opcode = 0xCB200000; // SUB X0, X0, W0, UXTB #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + Bits Op = new Bits(Opcode); + + AThreadState ThreadState; + + if (Rn != 31) + { + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + ThreadState = SingleOpcode(Opcode, X1: Xn_SP, X2: Wm, X31: _X31); + + AArch64.X((int)Rn, new Bits(Xn_SP)); + } + else + { + ThreadState = SingleOpcode(Opcode, X31: Xn_SP, X2: Wm); + + AArch64.SP(new Bits(Xn_SP)); + } + + AArch64.X((int)Rm, new Bits(Wm)); + Base.Sub_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); + + if (Rd != 31) + { + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + ulong SP = AArch64.SP(64).ToUInt64(); + + Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); + } + } + + [Test, Description("SUB , , {, {#}}")] + public void Sub_H_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn_SP, + [Values((ushort)0x0000, (ushort)0x7FFF, + (ushort)0x8000, (ushort)0xFFFF)] [Random(2)] ushort Wm, + [Values(0b000u, 0b001u, 0b010u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint Opcode = 0xCB200000; // SUB X0, X0, W0, UXTB #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + Bits Op = new Bits(Opcode); + + AThreadState ThreadState; + + if (Rn != 31) + { + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + ThreadState = SingleOpcode(Opcode, X1: Xn_SP, X2: Wm, X31: _X31); + + AArch64.X((int)Rn, new Bits(Xn_SP)); + } + else + { + ThreadState = SingleOpcode(Opcode, X31: Xn_SP, X2: Wm); + + AArch64.SP(new Bits(Xn_SP)); + } + + AArch64.X((int)Rm, new Bits(Wm)); + Base.Sub_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); + + if (Rd != 31) + { + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + ulong SP = AArch64.SP(64).ToUInt64(); + + Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); + } + } + + [Test, Description("SUB , , {, {#}}")] + public void Sub_B_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn_SP, + [Values((byte)0x00, (byte)0x7F, + (byte)0x80, (byte)0xFF)] [Random(2)] byte Wm, + [Values(0b000u, 0b001u, 0b010u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint Opcode = 0xCB200000; // SUB X0, X0, W0, UXTB #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + Bits Op = new Bits(Opcode); + + AThreadState ThreadState; + + if (Rn != 31) + { + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + ThreadState = SingleOpcode(Opcode, X1: Xn_SP, X2: Wm, X31: _X31); + + AArch64.X((int)Rn, new Bits(Xn_SP)); + } + else + { + ThreadState = SingleOpcode(Opcode, X31: Xn_SP, X2: Wm); + + AArch64.SP(new Bits(Xn_SP)); + } + + AArch64.X((int)Rm, new Bits(Wm)); + Base.Sub_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); + + if (Rd != 31) + { + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + ulong SP = AArch64.SP(64).ToUInt64(); + + Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); + } + } + + [Test, Description("SUB , , {, {#}}")] + public void Sub_W_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn_WSP, + [Values((uint)0x00000000, (uint)0x7FFFFFFF, + (uint)0x80000000, (uint)0xFFFFFFFF)] [Random(2)] uint Wm, + [Values(0b000u, 0b001u, 0b010u, 0b011u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint Opcode = 0x4B200000; // SUB W0, W0, W0, UXTB #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + Bits Op = new Bits(Opcode); + + AThreadState ThreadState; + + if (Rn != 31) + { + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + ThreadState = SingleOpcode(Opcode, X1: Wn_WSP, X2: Wm, X31: _W31); + + AArch64.X((int)Rn, new Bits(Wn_WSP)); + } + else + { + ThreadState = SingleOpcode(Opcode, X31: Wn_WSP, X2: Wm); + + AArch64.SP(new Bits(Wn_WSP)); + } + + AArch64.X((int)Rm, new Bits(Wm)); + Base.Sub_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); + + if (Rd != 31) + { + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + uint WSP = AArch64.SP(32).ToUInt32(); + + Assert.That((uint)ThreadState.X31, Is.EqualTo(WSP)); + } + } + + [Test, Description("SUB , , {, {#}}")] + public void Sub_H_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn_WSP, + [Values((ushort)0x0000, (ushort)0x7FFF, + (ushort)0x8000, (ushort)0xFFFF)] [Random(2)] ushort Wm, + [Values(0b000u, 0b001u, 0b010u, 0b011u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint Opcode = 0x4B200000; // SUB W0, W0, W0, UXTB #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + Bits Op = new Bits(Opcode); + + AThreadState ThreadState; + + if (Rn != 31) + { + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + ThreadState = SingleOpcode(Opcode, X1: Wn_WSP, X2: Wm, X31: _W31); + + AArch64.X((int)Rn, new Bits(Wn_WSP)); + } + else + { + ThreadState = SingleOpcode(Opcode, X31: Wn_WSP, X2: Wm); + + AArch64.SP(new Bits(Wn_WSP)); + } + + AArch64.X((int)Rm, new Bits(Wm)); + Base.Sub_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); + + if (Rd != 31) + { + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + uint WSP = AArch64.SP(32).ToUInt32(); + + Assert.That((uint)ThreadState.X31, Is.EqualTo(WSP)); + } + } + + [Test, Description("SUB , , {, {#}}")] + public void Sub_B_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn_WSP, + [Values((byte)0x00, (byte)0x7F, + (byte)0x80, (byte)0xFF)] [Random(2)] byte Wm, + [Values(0b000u, 0b001u, 0b010u, 0b011u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint Opcode = 0x4B200000; // SUB W0, W0, W0, UXTB #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + Bits Op = new Bits(Opcode); + + AThreadState ThreadState; + + if (Rn != 31) + { + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + ThreadState = SingleOpcode(Opcode, X1: Wn_WSP, X2: Wm, X31: _W31); + + AArch64.X((int)Rn, new Bits(Wn_WSP)); + } + else + { + ThreadState = SingleOpcode(Opcode, X31: Wn_WSP, X2: Wm); + + AArch64.SP(new Bits(Wn_WSP)); + } + + AArch64.X((int)Rm, new Bits(Wm)); + Base.Sub_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); + + if (Rd != 31) + { + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + uint WSP = AArch64.SP(32).ToUInt32(); + + Assert.That((uint)ThreadState.X31, Is.EqualTo(WSP)); + } + } + + [Test, Description("SUBS , , {, {#}}")] + public void Subs_X_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn_SP, + [Values((ulong)0x0000000000000000, (ulong)0x7FFFFFFFFFFFFFFF, + (ulong)0x8000000000000000, (ulong)0xFFFFFFFFFFFFFFFF)] [Random(2)] ulong Xm, + [Values(0b011u, 0b111u)] uint extend, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint Opcode = 0xEB206000; // SUBS X0, X0, X0, UXTX #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + Bits Op = new Bits(Opcode); + + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn_SP, X2: Xm, X31: Xn_SP); + + AArch64.X((int)Rn, new Bits(Xn_SP)); + AArch64.X((int)Rm, new Bits(Xm)); + AArch64.SP(new Bits(Xn_SP)); + Base.Subs_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); + + if (Rd != 31) + { + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + ulong _X31 = AArch64.SP(64).ToUInt64(); + + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + Assert.Multiple(() => + { + Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); + Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); + Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); + Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); + }); + } + + [Test, Description("SUBS , , {, {#}}")] + public void Subs_W_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn_SP, + [Values((uint)0x00000000, (uint)0x7FFFFFFF, + (uint)0x80000000, (uint)0xFFFFFFFF)] [Random(2)] uint Wm, + [Values(0b000u, 0b001u, 0b010u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint Opcode = 0xEB200000; // SUBS X0, X0, W0, UXTB #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + Bits Op = new Bits(Opcode); + + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn_SP, X2: Wm, X31: Xn_SP); + + AArch64.X((int)Rn, new Bits(Xn_SP)); + AArch64.X((int)Rm, new Bits(Wm)); + AArch64.SP(new Bits(Xn_SP)); + Base.Subs_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); + + if (Rd != 31) + { + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + ulong _X31 = AArch64.SP(64).ToUInt64(); + + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + Assert.Multiple(() => + { + Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); + Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); + Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); + Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); + }); + } + + [Test, Description("SUBS , , {, {#}}")] + public void Subs_H_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn_SP, + [Values((ushort)0x0000, (ushort)0x7FFF, + (ushort)0x8000, (ushort)0xFFFF)] [Random(2)] ushort Wm, + [Values(0b000u, 0b001u, 0b010u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint Opcode = 0xEB200000; // SUBS X0, X0, W0, UXTB #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + Bits Op = new Bits(Opcode); + + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn_SP, X2: Wm, X31: Xn_SP); + + AArch64.X((int)Rn, new Bits(Xn_SP)); + AArch64.X((int)Rm, new Bits(Wm)); + AArch64.SP(new Bits(Xn_SP)); + Base.Subs_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); + + if (Rd != 31) + { + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + ulong _X31 = AArch64.SP(64).ToUInt64(); + + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + Assert.Multiple(() => + { + Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); + Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); + Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); + Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); + }); + } + + [Test, Description("SUBS , , {, {#}}")] + public void Subs_B_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn_SP, + [Values((byte)0x00, (byte)0x7F, + (byte)0x80, (byte)0xFF)] [Random(2)] byte Wm, + [Values(0b000u, 0b001u, 0b010u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint Opcode = 0xEB200000; // SUBS X0, X0, W0, UXTB #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + Bits Op = new Bits(Opcode); + + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn_SP, X2: Wm, X31: Xn_SP); + + AArch64.X((int)Rn, new Bits(Xn_SP)); + AArch64.X((int)Rm, new Bits(Wm)); + AArch64.SP(new Bits(Xn_SP)); + Base.Subs_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); + + if (Rd != 31) + { + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + ulong _X31 = AArch64.SP(64).ToUInt64(); + + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + Assert.Multiple(() => + { + Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); + Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); + Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); + Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); + }); + } + + [Test, Description("SUBS , , {, {#}}")] + public void Subs_W_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn_WSP, + [Values((uint)0x00000000, (uint)0x7FFFFFFF, + (uint)0x80000000, (uint)0xFFFFFFFF)] [Random(2)] uint Wm, + [Values(0b000u, 0b001u, 0b010u, 0b011u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint Opcode = 0x6B200000; // SUBS W0, W0, W0, UXTB #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + Bits Op = new Bits(Opcode); + + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn_WSP, X2: Wm, X31: Wn_WSP); + + AArch64.X((int)Rn, new Bits(Wn_WSP)); + AArch64.X((int)Rm, new Bits(Wm)); + AArch64.SP(new Bits(Wn_WSP)); + Base.Subs_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); + + if (Rd != 31) + { + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + uint _W31 = AArch64.SP(32).ToUInt32(); + + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + Assert.Multiple(() => + { + Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); + Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); + Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); + Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); + }); + } + + [Test, Description("SUBS , , {, {#}}")] + public void Subs_H_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn_WSP, + [Values((ushort)0x0000, (ushort)0x7FFF, + (ushort)0x8000, (ushort)0xFFFF)] [Random(2)] ushort Wm, + [Values(0b000u, 0b001u, 0b010u, 0b011u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint Opcode = 0x6B200000; // SUBS W0, W0, W0, UXTB #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + Bits Op = new Bits(Opcode); + + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn_WSP, X2: Wm, X31: Wn_WSP); + + AArch64.X((int)Rn, new Bits(Wn_WSP)); + AArch64.X((int)Rm, new Bits(Wm)); + AArch64.SP(new Bits(Wn_WSP)); + Base.Subs_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); + + if (Rd != 31) + { + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + uint _W31 = AArch64.SP(32).ToUInt32(); + + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + Assert.Multiple(() => + { + Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); + Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); + Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); + Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); + }); + } + + [Test, Description("SUBS , , {, {#}}")] + public void Subs_B_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn_WSP, + [Values((byte)0x00, (byte)0x7F, + (byte)0x80, (byte)0xFF)] [Random(2)] byte Wm, + [Values(0b000u, 0b001u, 0b010u, 0b011u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint Opcode = 0x6B200000; // SUBS W0, W0, W0, UXTB #0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + Bits Op = new Bits(Opcode); + + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn_WSP, X2: Wm, X31: Wn_WSP); + + AArch64.X((int)Rn, new Bits(Wn_WSP)); + AArch64.X((int)Rm, new Bits(Wm)); + AArch64.SP(new Bits(Wn_WSP)); + Base.Subs_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); + + if (Rd != 31) + { + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + uint _W31 = AArch64.SP(32).ToUInt32(); + + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + Assert.Multiple(() => + { + Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); + Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); + Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); + Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); + }); + } +#endif + } +} diff --git a/Ryujinx.Tests/Cpu/CpuTestBfm.cs b/Ryujinx.Tests/Cpu/CpuTestBfm.cs new file mode 100644 index 000000000..2952bca4c --- /dev/null +++ b/Ryujinx.Tests/Cpu/CpuTestBfm.cs @@ -0,0 +1,213 @@ +//#define Bfm + +using ChocolArm64.State; + +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + using Tester; + using Tester.Types; + + [Category("Bfm"), Ignore("Tested: first half of 2018.")] + public sealed class CpuTestBfm : CpuTest + { +#if Bfm + [SetUp] + public void SetupTester() + { + AArch64.TakeReset(false); + } + + [Test, Description("BFM , , #, #")] + public void Bfm_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Random(2)] ulong _Xd, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(2)] ulong Xn, + [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, 2)] uint immr, + [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, 2)] uint imms) + { + uint Opcode = 0xB3400000; // BFM X0, X0, #0, #0 + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X0: _Xd, X1: Xn, X31: _X31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rd, new Bits(_Xd)); + AArch64.X((int)Rn, new Bits(Xn)); + Base.Bfm(Op[31], Op[22], Op[21, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + } + + [Test, Description("BFM , , #, #")] + public void Bfm_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Random(2)] uint _Wd, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(2)] uint Wn, + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 2)] uint immr, + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 2)] uint imms) + { + uint Opcode = 0x33000000; // BFM W0, W0, #0, #0 + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X0: _Wd, X1: Wn, X31: _W31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rd, new Bits(_Wd)); + AArch64.X((int)Rn, new Bits(Wn)); + Base.Bfm(Op[31], Op[22], Op[21, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + } + + [Test, Description("SBFM , , #, #")] + public void Sbfm_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(2)] ulong Xn, + [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, 2)] uint immr, + [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, 2)] uint imms) + { + uint Opcode = 0x93400000; // SBFM X0, X0, #0, #0 + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X31: _X31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Xn)); + Base.Sbfm(Op[31], Op[22], Op[21, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + } + + [Test, Description("SBFM , , #, #")] + public void Sbfm_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(2)] uint Wn, + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 2)] uint immr, + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 2)] uint imms) + { + uint Opcode = 0x13000000; // SBFM W0, W0, #0, #0 + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X31: _W31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Wn)); + Base.Sbfm(Op[31], Op[22], Op[21, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + } + + [Test, Description("UBFM , , #, #")] + public void Ubfm_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(2)] ulong Xn, + [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, 2)] uint immr, + [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, 2)] uint imms) + { + uint Opcode = 0xD3400000; // UBFM X0, X0, #0, #0 + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X31: _X31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Xn)); + Base.Ubfm(Op[31], Op[22], Op[21, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + } + + [Test, Description("UBFM , , #, #")] + public void Ubfm_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(2)] uint Wn, + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 2)] uint immr, + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 2)] uint imms) + { + uint Opcode = 0x53000000; // UBFM W0, W0, #0, #0 + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X31: _W31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Wn)); + Base.Ubfm(Op[31], Op[22], Op[21, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + } +#endif + } +} diff --git a/Ryujinx.Tests/Cpu/CpuTestCcmpImm.cs b/Ryujinx.Tests/Cpu/CpuTestCcmpImm.cs new file mode 100644 index 000000000..38d73878a --- /dev/null +++ b/Ryujinx.Tests/Cpu/CpuTestCcmpImm.cs @@ -0,0 +1,151 @@ +//#define CcmpImm + +using ChocolArm64.State; + +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + using Tester; + using Tester.Types; + + [Category("CcmpImm"), Ignore("Tested: first half of 2018.")] + public sealed class CpuTestCcmpImm : CpuTest + { +#if CcmpImm + [SetUp] + public void SetupTester() + { + AArch64.TakeReset(false); + } + + [Test, Description("CCMN , #, #, ")] + public void Ccmn_64bit([Values(1u, 31u)] uint Rn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn, + [Values(0u, 31u)] [Random(0u, 31u, 3)] uint imm, + [Random(0u, 15u, 1)] uint nzcv, + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + uint Opcode = 0xBA400800; // CCMN X0, #0, #0, EQ + Opcode |= ((Rn & 31) << 5); + Opcode |= ((imm & 31) << 16) | ((cond & 15) << 12) | ((nzcv & 15) << 0); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X31: _X31); + + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Xn)); + Base.Ccmn_Imm(Op[31], Op[20, 16], Op[15, 12], Op[9, 5], Op[3, 0]); + + Assert.Multiple(() => + { + Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); + Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); + Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); + Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); + }); + } + + [Test, Description("CCMN , #, #, ")] + public void Ccmn_32bit([Values(1u, 31u)] uint Rn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn, + [Values(0u, 31u)] [Random(0u, 31u, 3)] uint imm, + [Random(0u, 15u, 1)] uint nzcv, + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + uint Opcode = 0x3A400800; // CCMN W0, #0, #0, EQ + Opcode |= ((Rn & 31) << 5); + Opcode |= ((imm & 31) << 16) | ((cond & 15) << 12) | ((nzcv & 15) << 0); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X31: _W31); + + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Wn)); + Base.Ccmn_Imm(Op[31], Op[20, 16], Op[15, 12], Op[9, 5], Op[3, 0]); + + Assert.Multiple(() => + { + Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); + Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); + Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); + Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); + }); + } + + [Test, Description("CCMP , #, #, ")] + public void Ccmp_64bit([Values(1u, 31u)] uint Rn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn, + [Values(0u, 31u)] [Random(0u, 31u, 3)] uint imm, + [Random(0u, 15u, 1)] uint nzcv, + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + uint Opcode = 0xFA400800; // CCMP X0, #0, #0, EQ + Opcode |= ((Rn & 31) << 5); + Opcode |= ((imm & 31) << 16) | ((cond & 15) << 12) | ((nzcv & 15) << 0); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X31: _X31); + + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Xn)); + Base.Ccmp_Imm(Op[31], Op[20, 16], Op[15, 12], Op[9, 5], Op[3, 0]); + + Assert.Multiple(() => + { + Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); + Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); + Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); + Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); + }); + } + + [Test, Description("CCMP , #, #, ")] + public void Ccmp_32bit([Values(1u, 31u)] uint Rn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn, + [Values(0u, 31u)] [Random(0u, 31u, 3)] uint imm, + [Random(0u, 15u, 1)] uint nzcv, + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + uint Opcode = 0x7A400800; // CCMP W0, #0, #0, EQ + Opcode |= ((Rn & 31) << 5); + Opcode |= ((imm & 31) << 16) | ((cond & 15) << 12) | ((nzcv & 15) << 0); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X31: _W31); + + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Wn)); + Base.Ccmp_Imm(Op[31], Op[20, 16], Op[15, 12], Op[9, 5], Op[3, 0]); + + Assert.Multiple(() => + { + Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); + Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); + Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); + Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); + }); + } +#endif + } +} diff --git a/Ryujinx.Tests/Cpu/CpuTestCcmpReg.cs b/Ryujinx.Tests/Cpu/CpuTestCcmpReg.cs new file mode 100644 index 000000000..eb1c3abf2 --- /dev/null +++ b/Ryujinx.Tests/Cpu/CpuTestCcmpReg.cs @@ -0,0 +1,163 @@ +//#define CcmpReg + +using ChocolArm64.State; + +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + using Tester; + using Tester.Types; + + [Category("CcmpReg"), Ignore("Tested: first half of 2018.")] + public sealed class CpuTestCcmpReg : CpuTest + { +#if CcmpReg + [SetUp] + public void SetupTester() + { + AArch64.TakeReset(false); + } + + [Test, Description("CCMN , , #, ")] + public void Ccmn_64bit([Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xm, + [Random(0u, 15u, 1)] uint nzcv, + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + uint Opcode = 0xBA400000; // CCMN X0, X0, #0, EQ + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5); + Opcode |= ((cond & 15) << 12) | ((nzcv & 15) << 0); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Xn)); + AArch64.X((int)Rm, new Bits(Xm)); + Base.Ccmn_Reg(Op[31], Op[20, 16], Op[15, 12], Op[9, 5], Op[3, 0]); + + Assert.Multiple(() => + { + Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); + Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); + Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); + Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); + }); + } + + [Test, Description("CCMN , , #, ")] + public void Ccmn_32bit([Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wm, + [Random(0u, 15u, 1)] uint nzcv, + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + uint Opcode = 0x3A400000; // CCMN W0, W0, #0, EQ + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5); + Opcode |= ((cond & 15) << 12) | ((nzcv & 15) << 0); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Wn)); + AArch64.X((int)Rm, new Bits(Wm)); + Base.Ccmn_Reg(Op[31], Op[20, 16], Op[15, 12], Op[9, 5], Op[3, 0]); + + Assert.Multiple(() => + { + Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); + Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); + Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); + Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); + }); + } + + [Test, Description("CCMP , , #, ")] + public void Ccmp_64bit([Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xm, + [Random(0u, 15u, 1)] uint nzcv, + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + uint Opcode = 0xFA400000; // CCMP X0, X0, #0, EQ + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5); + Opcode |= ((cond & 15) << 12) | ((nzcv & 15) << 0); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Xn)); + AArch64.X((int)Rm, new Bits(Xm)); + Base.Ccmp_Reg(Op[31], Op[20, 16], Op[15, 12], Op[9, 5], Op[3, 0]); + + Assert.Multiple(() => + { + Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); + Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); + Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); + Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); + }); + } + + [Test, Description("CCMP , , #, ")] + public void Ccmp_32bit([Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wm, + [Random(0u, 15u, 1)] uint nzcv, + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + uint Opcode = 0x7A400000; // CCMP W0, W0, #0, EQ + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5); + Opcode |= ((cond & 15) << 12) | ((nzcv & 15) << 0); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Wn)); + AArch64.X((int)Rm, new Bits(Wm)); + Base.Ccmp_Reg(Op[31], Op[20, 16], Op[15, 12], Op[9, 5], Op[3, 0]); + + Assert.Multiple(() => + { + Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); + Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); + Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); + Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); + }); + } +#endif + } +} diff --git a/Ryujinx.Tests/Cpu/CpuTestCsel.cs b/Ryujinx.Tests/Cpu/CpuTestCsel.cs new file mode 100644 index 000000000..9dd61957f --- /dev/null +++ b/Ryujinx.Tests/Cpu/CpuTestCsel.cs @@ -0,0 +1,319 @@ +//#define Csel + +using ChocolArm64.State; + +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + using Tester; + using Tester.Types; + + [Category("Csel"), Ignore("Tested: first half of 2018.")] + public sealed class CpuTestCsel : CpuTest + { +#if Csel + [SetUp] + public void SetupTester() + { + AArch64.TakeReset(false); + } + + [Test, Description("CSEL , , , ")] + public void Csel_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xm, + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + uint Opcode = 0x9A800000; // CSEL X0, X0, X0, EQ + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((cond & 15) << 12); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Xn)); + AArch64.X((int)Rm, new Bits(Xm)); + Base.Csel(Op[31], Op[20, 16], Op[15, 12], Op[9, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + } + + [Test, Description("CSEL , , , ")] + public void Csel_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wm, + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + uint Opcode = 0x1A800000; // CSEL W0, W0, W0, EQ + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((cond & 15) << 12); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Wn)); + AArch64.X((int)Rm, new Bits(Wm)); + Base.Csel(Op[31], Op[20, 16], Op[15, 12], Op[9, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + } + + [Test, Description("CSINC , , , ")] + public void Csinc_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xm, + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + uint Opcode = 0x9A800400; // CSINC X0, X0, X0, EQ + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((cond & 15) << 12); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Xn)); + AArch64.X((int)Rm, new Bits(Xm)); + Base.Csinc(Op[31], Op[20, 16], Op[15, 12], Op[9, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + } + + [Test, Description("CSINC , , , ")] + public void Csinc_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wm, + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + uint Opcode = 0x1A800400; // CSINC W0, W0, W0, EQ + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((cond & 15) << 12); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Wn)); + AArch64.X((int)Rm, new Bits(Wm)); + Base.Csinc(Op[31], Op[20, 16], Op[15, 12], Op[9, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + } + + [Test, Description("CSINV , , , ")] + public void Csinv_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xm, + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + uint Opcode = 0xDA800000; // CSINV X0, X0, X0, EQ + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((cond & 15) << 12); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Xn)); + AArch64.X((int)Rm, new Bits(Xm)); + Base.Csinv(Op[31], Op[20, 16], Op[15, 12], Op[9, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + } + + [Test, Description("CSINV , , , ")] + public void Csinv_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wm, + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + uint Opcode = 0x5A800000; // CSINV W0, W0, W0, EQ + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((cond & 15) << 12); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Wn)); + AArch64.X((int)Rm, new Bits(Wm)); + Base.Csinv(Op[31], Op[20, 16], Op[15, 12], Op[9, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + } + + [Test, Description("CSNEG , , , ")] + public void Csneg_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xm, + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + uint Opcode = 0xDA800400; // CSNEG X0, X0, X0, EQ + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((cond & 15) << 12); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Xn)); + AArch64.X((int)Rm, new Bits(Xm)); + Base.Csneg(Op[31], Op[20, 16], Op[15, 12], Op[9, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + } + + [Test, Description("CSNEG , , , ")] + public void Csneg_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wm, + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + uint Opcode = 0x5A800400; // CSNEG W0, W0, W0, EQ + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((cond & 15) << 12); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Wn)); + AArch64.X((int)Rm, new Bits(Wm)); + Base.Csneg(Op[31], Op[20, 16], Op[15, 12], Op[9, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + } +#endif + } +} diff --git a/Ryujinx.Tests/Cpu/CpuTestMisc.cs b/Ryujinx.Tests/Cpu/CpuTestMisc.cs index 3a995fe26..647d46c00 100644 --- a/Ryujinx.Tests/Cpu/CpuTestMisc.cs +++ b/Ryujinx.Tests/Cpu/CpuTestMisc.cs @@ -1,22 +1,12 @@ using ChocolArm64.State; + using NUnit.Framework; namespace Ryujinx.Tests.Cpu { - public class CpuTestMisc : CpuTest + [Category("Misc"), Explicit] + public sealed class CpuTestMisc : CpuTest { - [TestCase(0ul)] - [TestCase(1ul)] - [TestCase(2ul)] - [TestCase(42ul)] - public void SanityCheck(ulong A) - { - // NOP - uint Opcode = 0xD503201F; - AThreadState ThreadState = SingleOpcode(Opcode, X0: A); - Assert.AreEqual(A, ThreadState.X0); - } - [TestCase(0xFFFFFFFDu)] // Roots. [TestCase(0x00000005u)] public void Misc1(uint A) @@ -46,27 +36,28 @@ namespace Ryujinx.Tests.Cpu Opcode(0xD4200000); Opcode(0xD65F03C0); ExecuteOpcodes(); - Assert.AreEqual(0, GetThreadState().X0); + + Assert.That(GetThreadState().X0, Is.Zero); } - [TestCase(-20f, -5f)] // 18 integer solutions. - [TestCase(-12f, -6f)] - [TestCase(-12f, 3f)] - [TestCase(-8f, -8f)] - [TestCase(-6f, -12f)] - [TestCase(-5f, -20f)] - [TestCase(-4f, 2f)] - [TestCase(-3f, 12f)] - [TestCase(-2f, 4f)] - [TestCase(2f, -4f)] - [TestCase(3f, -12f)] - [TestCase(4f, -2f)] - [TestCase(5f, 20f)] - [TestCase(6f, 12f)] - [TestCase(8f, 8f)] - [TestCase(12f, -3f)] - [TestCase(12f, 6f)] - [TestCase(20f, 5f)] + [TestCase(-20f, -5f)] // 18 integer solutions. + [TestCase(-12f, -6f)] + [TestCase(-12f, 3f)] + [TestCase( -8f, -8f)] + [TestCase( -6f, -12f)] + [TestCase( -5f, -20f)] + [TestCase( -4f, 2f)] + [TestCase( -3f, 12f)] + [TestCase( -2f, 4f)] + [TestCase( 2f, -4f)] + [TestCase( 3f, -12f)] + [TestCase( 4f, -2f)] + [TestCase( 5f, 20f)] + [TestCase( 6f, 12f)] + [TestCase( 8f, 8f)] + [TestCase( 12f, -3f)] + [TestCase( 12f, 6f)] + [TestCase( 20f, 5f)] public void Misc2(float A, float B) { // 1 / ((1 / A + 1 / B) ^ 2) = 16 @@ -92,27 +83,28 @@ namespace Ryujinx.Tests.Cpu Opcode(0xD4200000); Opcode(0xD65F03C0); ExecuteOpcodes(); - Assert.AreEqual(16f, GetThreadState().V0.S0); + + Assert.That(GetThreadState().V0.S0, Is.EqualTo(16f)); } - [TestCase(-20d, -5d)] // 18 integer solutions. - [TestCase(-12d, -6d)] - [TestCase(-12d, 3d)] - [TestCase(-8d, -8d)] - [TestCase(-6d, -12d)] - [TestCase(-5d, -20d)] - [TestCase(-4d, 2d)] - [TestCase(-3d, 12d)] - [TestCase(-2d, 4d)] - [TestCase(2d, -4d)] - [TestCase(3d, -12d)] - [TestCase(4d, -2d)] - [TestCase(5d, 20d)] - [TestCase(6d, 12d)] - [TestCase(8d, 8d)] - [TestCase(12d, -3d)] - [TestCase(12d, 6d)] - [TestCase(20d, 5d)] + [TestCase(-20d, -5d)] // 18 integer solutions. + [TestCase(-12d, -6d)] + [TestCase(-12d, 3d)] + [TestCase( -8d, -8d)] + [TestCase( -6d, -12d)] + [TestCase( -5d, -20d)] + [TestCase( -4d, 2d)] + [TestCase( -3d, 12d)] + [TestCase( -2d, 4d)] + [TestCase( 2d, -4d)] + [TestCase( 3d, -12d)] + [TestCase( 4d, -2d)] + [TestCase( 5d, 20d)] + [TestCase( 6d, 12d)] + [TestCase( 8d, 8d)] + [TestCase( 12d, -3d)] + [TestCase( 12d, 6d)] + [TestCase( 20d, 5d)] public void Misc3(double A, double B) { // 1 / ((1 / A + 1 / B) ^ 2) = 16 @@ -138,74 +130,12 @@ namespace Ryujinx.Tests.Cpu Opcode(0xD4200000); Opcode(0xD65F03C0); ExecuteOpcodes(); - Assert.AreEqual(16d, GetThreadState().V0.D0); + + Assert.That(GetThreadState().V0.D0, Is.EqualTo(16d)); } [Test] - public void MiscR() - { - ulong Result = 5; - - /* - 0x0000000000000000: MOV X0, #2 - 0x0000000000000004: MOV X1, #3 - 0x0000000000000008: ADD X0, X0, X1 - 0x000000000000000C: BRK #0 - 0x0000000000000010: RET - */ - - Opcode(0xD2800040); - Opcode(0xD2800061); - Opcode(0x8B010000); - Opcode(0xD4200000); - Opcode(0xD65F03C0); - ExecuteOpcodes(); - Assert.AreEqual(Result, GetThreadState().X0); - - Reset(); - - /* - 0x0000000000000000: MOV X0, #3 - 0x0000000000000004: MOV X1, #2 - 0x0000000000000008: ADD X0, X0, X1 - 0x000000000000000C: BRK #0 - 0x0000000000000010: RET - */ - - Opcode(0xD2800060); - Opcode(0xD2800041); - Opcode(0x8B010000); - Opcode(0xD4200000); - Opcode(0xD65F03C0); - ExecuteOpcodes(); - Assert.AreEqual(Result, GetThreadState().X0); - } - - [Test, Explicit] - public void Misc5() - { - /* - 0x0000000000000000: SUBS X0, X0, #1 - 0x0000000000000004: B.NE #0 - 0x0000000000000008: BRK #0 - 0x000000000000000C: RET - */ - - SetThreadState(X0: 0x100000000); - Opcode(0xF1000400); - Opcode(0x54FFFFE1); - Opcode(0xD4200000); - Opcode(0xD65F03C0); - ExecuteOpcodes(); - Assert.Multiple(() => - { - Assert.AreEqual(0, GetThreadState().X0); - Assert.IsTrue(GetThreadState().Zero); - }); - } - - [Test] - public void MiscF([Range(0, 92, 1)] int A) + public void MiscF([Range(0u, 92u, 1u)] uint A) { ulong F_n(uint n) { @@ -250,7 +180,7 @@ namespace Ryujinx.Tests.Cpu 0x0000000000000050: RET */ - SetThreadState(X0: (uint)A); + SetThreadState(X0: A); Opcode(0x2A0003E4); Opcode(0x340001C0); Opcode(0x7100041F); @@ -273,7 +203,62 @@ namespace Ryujinx.Tests.Cpu Opcode(0xD4200000); Opcode(0xD65F03C0); ExecuteOpcodes(); - Assert.AreEqual(F_n((uint)A), GetThreadState().X0); + + Assert.That(GetThreadState().X0, Is.EqualTo(F_n(A))); + } + + [Test] + public void MiscR() + { + const ulong Result = 5; + + /* + 0x0000000000000000: MOV X0, #2 + 0x0000000000000004: MOV X1, #3 + 0x0000000000000008: ADD X0, X0, X1 + 0x000000000000000C: BRK #0 + 0x0000000000000010: RET + */ + + Opcode(0xD2800040); + Opcode(0xD2800061); + Opcode(0x8B010000); + Opcode(0xD4200000); + Opcode(0xD65F03C0); + ExecuteOpcodes(); + + Assert.That(GetThreadState().X0, Is.EqualTo(Result)); + + Reset(); + + /* + 0x0000000000000000: MOV X0, #3 + 0x0000000000000004: MOV X1, #2 + 0x0000000000000008: ADD X0, X0, X1 + 0x000000000000000C: BRK #0 + 0x0000000000000010: RET + */ + + Opcode(0xD2800060); + Opcode(0xD2800041); + Opcode(0x8B010000); + Opcode(0xD4200000); + Opcode(0xD65F03C0); + ExecuteOpcodes(); + + Assert.That(GetThreadState().X0, Is.EqualTo(Result)); + } + + [TestCase( 0ul)] + [TestCase( 1ul)] + [TestCase( 2ul)] + [TestCase(42ul)] + public void SanityCheck(ulong A) + { + uint Opcode = 0xD503201F; // NOP + AThreadState ThreadState = SingleOpcode(Opcode, X0: A); + + Assert.That(ThreadState.X0, Is.EqualTo(A)); } } } diff --git a/Ryujinx.Tests/Cpu/CpuTestMov.cs b/Ryujinx.Tests/Cpu/CpuTestMov.cs new file mode 100644 index 000000000..9c7e3255a --- /dev/null +++ b/Ryujinx.Tests/Cpu/CpuTestMov.cs @@ -0,0 +1,189 @@ +//#define Mov + +using ChocolArm64.State; + +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + using Tester; + using Tester.Types; + + [Category("Mov"), Ignore("Tested: first half of 2018.")] + public sealed class CpuTestMov : CpuTest + { +#if Mov + [SetUp] + public void SetupTester() + { + AArch64.TakeReset(false); + } + + [Test, Description("MOVK , #{, LSL #}")] + public void Movk_64bit([Values(0u, 31u)] uint Rd, + [Random(12)] ulong _Xd, + [Values(0u, 65535u)] [Random(0u, 65535u, 10)] uint imm, + [Values(0u, 16u, 32u, 48u)] uint shift) + { + uint Opcode = 0xF2800000; // MOVK X0, #0, LSL #0 + Opcode |= ((Rd & 31) << 0); + Opcode |= (((shift / 16) & 3) << 21) | ((imm & 65535) << 5); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X0: _Xd, X31: _X31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rd, new Bits(_Xd)); + Base.Movk(Op[31], Op[22, 21], Op[20, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + } + + [Test, Description("MOVK , #{, LSL #}")] + public void Movk_32bit([Values(0u, 31u)] uint Rd, + [Random(12)] uint _Wd, + [Values(0u, 65535u)] [Random(0u, 65535u, 10)] uint imm, + [Values(0u, 16u)] uint shift) + { + uint Opcode = 0x72800000; // MOVK W0, #0, LSL #0 + Opcode |= ((Rd & 31) << 0); + Opcode |= (((shift / 16) & 3) << 21) | ((imm & 65535) << 5); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X0: _Wd, X31: _W31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rd, new Bits(_Wd)); + Base.Movk(Op[31], Op[22, 21], Op[20, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + } + + [Test, Description("MOVN , #{, LSL #}")] + public void Movn_64bit([Values(0u, 31u)] uint Rd, + [Values(0u, 65535u)] [Random(0u, 65535u, 128)] uint imm, + [Values(0u, 16u, 32u, 48u)] uint shift) + { + uint Opcode = 0x92800000; // MOVN X0, #0, LSL #0 + Opcode |= ((Rd & 31) << 0); + Opcode |= (((shift / 16) & 3) << 21) | ((imm & 65535) << 5); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X31: _X31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + Base.Movn(Op[31], Op[22, 21], Op[20, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + } + + [Test, Description("MOVN , #{, LSL #}")] + public void Movn_32bit([Values(0u, 31u)] uint Rd, + [Values(0u, 65535u)] [Random(0u, 65535u, 128)] uint imm, + [Values(0u, 16u)] uint shift) + { + uint Opcode = 0x12800000; // MOVN W0, #0, LSL #0 + Opcode |= ((Rd & 31) << 0); + Opcode |= (((shift / 16) & 3) << 21) | ((imm & 65535) << 5); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X31: _W31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + Base.Movn(Op[31], Op[22, 21], Op[20, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + } + + [Test, Description("MOVZ , #{, LSL #}")] + public void Movz_64bit([Values(0u, 31u)] uint Rd, + [Values(0u, 65535u)] [Random(0u, 65535u, 128)] uint imm, + [Values(0u, 16u, 32u, 48u)] uint shift) + { + uint Opcode = 0xD2800000; // MOVZ X0, #0, LSL #0 + Opcode |= ((Rd & 31) << 0); + Opcode |= (((shift / 16) & 3) << 21) | ((imm & 65535) << 5); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X31: _X31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + Base.Movz(Op[31], Op[22, 21], Op[20, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + } + + [Test, Description("MOVZ , #{, LSL #}")] + public void Movz_32bit([Values(0u, 31u)] uint Rd, + [Values(0u, 65535u)] [Random(0u, 65535u, 128)] uint imm, + [Values(0u, 16u)] uint shift) + { + uint Opcode = 0x52800000; // MOVZ W0, #0, LSL #0 + Opcode |= ((Rd & 31) << 0); + Opcode |= (((shift / 16) & 3) << 21) | ((imm & 65535) << 5); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X31: _W31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + Base.Movz(Op[31], Op[22, 21], Op[20, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + } +#endif + } +} diff --git a/Ryujinx.Tests/Cpu/CpuTestMul.cs b/Ryujinx.Tests/Cpu/CpuTestMul.cs new file mode 100644 index 000000000..9bdc1fa65 --- /dev/null +++ b/Ryujinx.Tests/Cpu/CpuTestMul.cs @@ -0,0 +1,375 @@ +//#define Mul + +using ChocolArm64.State; + +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + using Tester; + using Tester.Types; + + [Category("Mul"), Ignore("Tested: first half of 2018.")] + public sealed class CpuTestMul : CpuTest + { +#if Mul + [SetUp] + public void SetupTester() + { + AArch64.TakeReset(false); + } + + [Test, Description("MADD , , , ")] + public void Madd_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(3u, 31u)] uint Ra, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(2)] ulong Xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(2)] ulong Xm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(2)] ulong Xa) + { + uint Opcode = 0x9B000000; // MADD X0, X0, X0, X0 + Opcode |= ((Rm & 31) << 16) | ((Ra & 31) << 10) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X3: Xa, X31: _X31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Xn)); + AArch64.X((int)Rm, new Bits(Xm)); + AArch64.X((int)Ra, new Bits(Xa)); + Base.Madd(Op[31], Op[20, 16], Op[14, 10], Op[9, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + } + + [Test, Description("MADD , , , ")] + public void Madd_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(3u, 31u)] uint Ra, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(2)] uint Wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(2)] uint Wm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(2)] uint Wa) + { + uint Opcode = 0x1B000000; // MADD W0, W0, W0, W0 + Opcode |= ((Rm & 31) << 16) | ((Ra & 31) << 10) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X3: Wa, X31: _W31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Wn)); + AArch64.X((int)Rm, new Bits(Wm)); + AArch64.X((int)Ra, new Bits(Wa)); + Base.Madd(Op[31], Op[20, 16], Op[14, 10], Op[9, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + } + + [Test, Description("MSUB , , , ")] + public void Msub_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(3u, 31u)] uint Ra, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(2)] ulong Xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(2)] ulong Xm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(2)] ulong Xa) + { + uint Opcode = 0x9B008000; // MSUB X0, X0, X0, X0 + Opcode |= ((Rm & 31) << 16) | ((Ra & 31) << 10) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X3: Xa, X31: _X31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Xn)); + AArch64.X((int)Rm, new Bits(Xm)); + AArch64.X((int)Ra, new Bits(Xa)); + Base.Msub(Op[31], Op[20, 16], Op[14, 10], Op[9, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + } + + [Test, Description("MSUB , , , ")] + public void Msub_32bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(3u, 31u)] uint Ra, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(2)] uint Wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(2)] uint Wm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(2)] uint Wa) + { + uint Opcode = 0x1B008000; // MSUB W0, W0, W0, W0 + Opcode |= ((Rm & 31) << 16) | ((Ra & 31) << 10) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X3: Wa, X31: _W31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Wn)); + AArch64.X((int)Rm, new Bits(Wm)); + AArch64.X((int)Ra, new Bits(Wa)); + Base.Msub(Op[31], Op[20, 16], Op[14, 10], Op[9, 5], Op[4, 0]); + uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + + Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); + } + else + { + Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + } + } + + [Test, Description("SMADDL , , , ")] + public void Smaddl_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(3u, 31u)] uint Ra, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(2)] uint Wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(2)] uint Wm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(2)] ulong Xa) + { + uint Opcode = 0x9B200000; // SMADDL X0, W0, W0, X0 + Opcode |= ((Rm & 31) << 16) | ((Ra & 31) << 10) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X3: Xa, X31: _X31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Wn)); + AArch64.X((int)Rm, new Bits(Wm)); + AArch64.X((int)Ra, new Bits(Xa)); + Base.Smaddl(Op[20, 16], Op[14, 10], Op[9, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + } + + [Test, Description("UMADDL , , , ")] + public void Umaddl_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(3u, 31u)] uint Ra, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(2)] uint Wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(2)] uint Wm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(2)] ulong Xa) + { + uint Opcode = 0x9BA00000; // UMADDL X0, W0, W0, X0 + Opcode |= ((Rm & 31) << 16) | ((Ra & 31) << 10) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X3: Xa, X31: _X31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Wn)); + AArch64.X((int)Rm, new Bits(Wm)); + AArch64.X((int)Ra, new Bits(Xa)); + Base.Umaddl(Op[20, 16], Op[14, 10], Op[9, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + } + + [Test, Description("SMSUBL , , , ")] + public void Smsubl_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(3u, 31u)] uint Ra, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(2)] uint Wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(2)] uint Wm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(2)] ulong Xa) + { + uint Opcode = 0x9B208000; // SMSUBL X0, W0, W0, X0 + Opcode |= ((Rm & 31) << 16) | ((Ra & 31) << 10) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X3: Xa, X31: _X31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Wn)); + AArch64.X((int)Rm, new Bits(Wm)); + AArch64.X((int)Ra, new Bits(Xa)); + Base.Smsubl(Op[20, 16], Op[14, 10], Op[9, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + } + + [Test, Description("UMSUBL , , , ")] + public void Umsubl_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(3u, 31u)] uint Ra, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(2)] uint Wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(2)] uint Wm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(2)] ulong Xa) + { + uint Opcode = 0x9BA08000; // UMSUBL X0, W0, W0, X0 + Opcode |= ((Rm & 31) << 16) | ((Ra & 31) << 10) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X3: Xa, X31: _X31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Wn)); + AArch64.X((int)Rm, new Bits(Wm)); + AArch64.X((int)Ra, new Bits(Xa)); + Base.Umsubl(Op[20, 16], Op[14, 10], Op[9, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + } + + [Test, Description("SMULH , , ")] + public void Smulh_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(16)] ulong Xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(16)] ulong Xm) + { + uint Opcode = 0x9B407C00; // SMULH X0, X0, X0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Xn)); + AArch64.X((int)Rm, new Bits(Xm)); + Base.Smulh(Op[20, 16], Op[9, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + } + + [Test, Description("UMULH , , ")] + public void Umulh_64bit([Values(0u, 31u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [Values(2u, 31u)] uint Rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(16)] ulong Xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(16)] ulong Xm) + { + uint Opcode = 0x9BC07C00; // UMULH X0, X0, X0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + + if (Rd != 31) + { + Bits Op = new Bits(Opcode); + + AArch64.X((int)Rn, new Bits(Xn)); + AArch64.X((int)Rm, new Bits(Xm)); + Base.Umulh(Op[20, 16], Op[9, 5], Op[4, 0]); + ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + + Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); + } + else + { + Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + } + } +#endif + } +} diff --git a/Ryujinx.Tests/Cpu/CpuTestScalar.cs b/Ryujinx.Tests/Cpu/CpuTestScalar.cs index a178be272..c7c3aa051 100644 --- a/Ryujinx.Tests/Cpu/CpuTestScalar.cs +++ b/Ryujinx.Tests/Cpu/CpuTestScalar.cs @@ -5,24 +5,52 @@ namespace Ryujinx.Tests.Cpu { public class CpuTestScalar : CpuTest { - [TestCase(0x00000000u, 0x80000000u, 0x00000000u)] - [TestCase(0x80000000u, 0x00000000u, 0x00000000u)] - [TestCase(0x80000000u, 0x80000000u, 0x80000000u)] - [TestCase(0x3DCCCCCDu, 0x3C9623B1u, 0x3DCCCCCDu)] - [TestCase(0x8BA98D27u, 0x00000076u, 0x00000076u)] - [TestCase(0x807FFFFFu, 0x7F7FFFFFu, 0x7F7FFFFFu)] - [TestCase(0x7F7FFFFFu, 0x807FFFFFu, 0x7F7FFFFFu)] - [TestCase(0x7FC00000u, 0x3F800000u, 0x7FC00000u)] - [TestCase(0x3F800000u, 0x7FC00000u, 0x7FC00000u)] - [TestCase(0x7F800001u, 0x7FC00042u, 0x7FC00001u)] - [TestCase(0x7FC00042u, 0x7F800001u, 0x7FC00001u)] - [TestCase(0x7FC0000Au, 0x7FC0000Bu, 0x7FC0000Au)] - public void Fmax_S(uint A, uint B, uint Result) + [TestCase(0x1E224820u, 0x0000000000000000ul, 0x0000000080000000ul, 0x0000000000000000ul)] + [TestCase(0x1E224820u, 0x0000000080000000ul, 0x0000000000000000ul, 0x0000000000000000ul)] + [TestCase(0x1E224820u, 0x0000000080000000ul, 0x0000000080000000ul, 0x0000000080000000ul)] + [TestCase(0x1E224820u, 0x0000000080000000ul, 0x000000003DCCCCCDul, 0x000000003DCCCCCDul)] + [TestCase(0x1E224820u, 0x000000003DCCCCCDul, 0x000000003C9623B1ul, 0x000000003DCCCCCDul)] + [TestCase(0x1E224820u, 0x000000008BA98D27ul, 0x0000000000000076ul, 0x0000000000000076ul)] + [TestCase(0x1E224820u, 0x00000000807FFFFFul, 0x000000007F7FFFFFul, 0x000000007F7FFFFFul)] + [TestCase(0x1E224820u, 0x000000007F7FFFFFul, 0x00000000807FFFFFul, 0x000000007F7FFFFFul)] + [TestCase(0x1E224820u, 0x000000007FC00000ul, 0x000000003F800000ul, 0x000000007FC00000ul)] + [TestCase(0x1E224820u, 0x000000003F800000ul, 0x000000007FC00000ul, 0x000000007FC00000ul)] + [TestCase(0x1E224820u, 0x000000007F800001ul, 0x000000007FC00042ul, 0x000000007FC00001ul, Ignore = "NaN test.")] + [TestCase(0x1E224820u, 0x000000007FC00042ul, 0x000000007F800001ul, 0x000000007FC00001ul, Ignore = "NaN test.")] + [TestCase(0x1E224820u, 0x000000007FC0000Aul, 0x000000007FC0000Bul, 0x000000007FC0000Aul, Ignore = "NaN test.")] + [TestCase(0x1E624820u, 0x0000000000000000ul, 0x8000000000000000ul, 0x0000000000000000ul)] + [TestCase(0x1E624820u, 0x8000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul)] + [TestCase(0x1E624820u, 0x8000000000000000ul, 0x8000000000000000ul, 0x8000000000000000ul)] + [TestCase(0x1E624820u, 0x8000000000000000ul, 0x3FF3333333333333ul, 0x3FF3333333333333ul)] + public void Fmax_S(uint Opcode, ulong A, ulong B, ulong Result) { // FMAX S0, S1, S2 - uint Opcode = 0x1E224820; - AThreadState ThreadState = SingleOpcode(Opcode, V1: new AVec { W0 = A }, V2: new AVec { W0 = B }); - Assert.AreEqual(Result, ThreadState.V0.W0); + AThreadState ThreadState = SingleOpcode(Opcode, V1: new AVec { X0 = A }, V2: new AVec { X0 = B }); + Assert.AreEqual(Result, ThreadState.V0.X0); + } + + [TestCase(0x1E225820u, 0x0000000000000000ul, 0x0000000080000000ul, 0x0000000080000000ul)] + [TestCase(0x1E225820u, 0x0000000080000000ul, 0x0000000000000000ul, 0x0000000080000000ul)] + [TestCase(0x1E225820u, 0x0000000080000000ul, 0x0000000080000000ul, 0x0000000080000000ul)] + [TestCase(0x1E225820u, 0x0000000080000000ul, 0x000000003DCCCCCDul, 0x0000000080000000ul)] + [TestCase(0x1E225820u, 0x000000003DCCCCCDul, 0x000000003C9623B1ul, 0x000000003C9623B1ul)] + [TestCase(0x1E225820u, 0x000000008BA98D27ul, 0x0000000000000076ul, 0x000000008BA98D27ul)] + [TestCase(0x1E225820u, 0x00000000807FFFFFul, 0x000000007F7FFFFFul, 0x00000000807FFFFFul)] + [TestCase(0x1E225820u, 0x000000007F7FFFFFul, 0x00000000807FFFFFul, 0x00000000807FFFFFul)] + [TestCase(0x1E225820u, 0x000000007FC00000ul, 0x000000003F800000ul, 0x000000007FC00000ul)] + [TestCase(0x1E225820u, 0x000000003F800000ul, 0x000000007FC00000ul, 0x000000007FC00000ul)] + [TestCase(0x1E225820u, 0x000000007F800001ul, 0x000000007FC00042ul, 0x000000007FC00001ul, Ignore = "NaN test.")] + [TestCase(0x1E225820u, 0x000000007FC00042ul, 0x000000007F800001ul, 0x000000007FC00001ul, Ignore = "NaN test.")] + [TestCase(0x1E225820u, 0x000000007FC0000Aul, 0x000000007FC0000Bul, 0x000000007FC0000Aul, Ignore = "NaN test.")] + [TestCase(0x1E625820u, 0x0000000000000000ul, 0x8000000000000000ul, 0x8000000000000000ul)] + [TestCase(0x1E625820u, 0x8000000000000000ul, 0x0000000000000000ul, 0x8000000000000000ul)] + [TestCase(0x1E625820u, 0x8000000000000000ul, 0x8000000000000000ul, 0x8000000000000000ul)] + [TestCase(0x1E625820u, 0x8000000000000000ul, 0x3FF3333333333333ul, 0x8000000000000000ul)] + public void Fmin_S(uint Opcode, ulong A, ulong B, ulong Result) + { + // FMIN S0, S1, S2 + AThreadState ThreadState = SingleOpcode(Opcode, V1: new AVec { X0 = A }, V2: new AVec { X0 = B }); + Assert.AreEqual(Result, ThreadState.V0.X0); } } } diff --git a/Ryujinx.Tests/Cpu/CpuTestSimd.cs b/Ryujinx.Tests/Cpu/CpuTestSimd.cs new file mode 100644 index 000000000..5fa3a72df --- /dev/null +++ b/Ryujinx.Tests/Cpu/CpuTestSimd.cs @@ -0,0 +1,242 @@ +#define Simd + +using ChocolArm64.State; + +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + using Tester; + using Tester.Types; + + [Category("Simd")] + public sealed class CpuTestSimd : CpuTest + { +#if Simd + [SetUp] + public void SetupTester() + { + AArch64.TakeReset(false); + } + +#region "ValueSource" + private static ulong[] _1D_() + { + return new ulong[] { 0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul }; + } + + private static ulong[] _8B4H_() + { + return new ulong[] { 0x0000000000000000ul, 0x7F7F7F7F7F7F7F7Ful, + 0x8080808080808080ul, 0x7FFF7FFF7FFF7FFFul, + 0x8000800080008000ul, 0xFFFFFFFFFFFFFFFFul }; + } + + private static ulong[] _8B4H2S_() + { + return new ulong[] { 0x0000000000000000ul, 0x7F7F7F7F7F7F7F7Ful, + 0x8080808080808080ul, 0x7FFF7FFF7FFF7FFFul, + 0x8000800080008000ul, 0x7FFFFFFF7FFFFFFFul, + 0x8000000080000000ul, 0xFFFFFFFFFFFFFFFFul }; + } + + private static ulong[] _8B4H2S1D_() + { + return new ulong[] { 0x0000000000000000ul, 0x7F7F7F7F7F7F7F7Ful, + 0x8080808080808080ul, 0x7FFF7FFF7FFF7FFFul, + 0x8000800080008000ul, 0x7FFFFFFF7FFFFFFFul, + 0x8000000080000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul }; + } +#endregion + + [Test, Description("ABS , ")] + public void Abs_S_D([ValueSource("_1D_")] [Random(1)] ulong A) + { + uint Opcode = 0x5EE0B820; // ABS D0, D1 + Bits Op = new Bits(Opcode); + + AVec V0 = new AVec { X1 = TestContext.CurrentContext.Random.NextULong() }; + AVec V1 = new AVec { X0 = A }; + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + + AArch64.V(1, new Bits(A)); + 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); + } + + [Test, Description("ABS ., .")] + public void Abs_V_8B_4H_2S([ValueSource("_8B4H2S_")] [Random(1)] ulong A, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint Opcode = 0x0E20B820; // ABS V0.8B, V1.8B + Opcode |= ((size & 3) << 22); + Bits Op = new Bits(Opcode); + + AVec V0 = new AVec { X1 = TestContext.CurrentContext.Random.NextULong() }; + AVec V1 = new AVec { X0 = A }; + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + + AArch64.V(1, new Bits(A)); + 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); + } + + [Test, Pairwise, Description("ABS ., .")] + public void Abs_V_16B_8H_4S_2D([ValueSource("_8B4H2S1D_")] [Random(1)] ulong A0, + [ValueSource("_8B4H2S1D_")] [Random(1)] ulong A1, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint Opcode = 0x4E20B820; // ABS V0.16B, V1.16B + Opcode |= ((size & 3) << 22); + Bits Op = new Bits(Opcode); + + AVec V1 = new AVec { X0 = A0, X1 = A1 }; + AThreadState ThreadState = SingleOpcode(Opcode, V1: V1); + + AArch64.Vpart(1, 0, new Bits(A0)); + AArch64.Vpart(1, 1, new Bits(A1)); + SimdFp.Abs_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); + + Assert.Multiple(() => + { + Assert.That(ThreadState.V0.X0, Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); + Assert.That(ThreadState.V0.X1, Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); + }); + } + + [Test, Pairwise, Description("ADDP , .")] + public void Addp_S_2DD([ValueSource("_1D_")] [Random(1)] ulong A0, + [ValueSource("_1D_")] [Random(1)] ulong A1) + { + uint Opcode = 0x5EF1B820; // ADDP D0, V1.2D + 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.Addp_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); + }); + } + + [Test, Description("ADDV , .")] + public void Addv_V_8BB_4HH([ValueSource("_8B4H_")] [Random(1)] ulong A, + [Values(0b00u, 0b01u)] uint size) // <8B, 4H> + { + uint Opcode = 0x0E31B820; // ADDV B0, V1.8B + 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.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); + } + + [Test, Pairwise, Description("ADDV , .")] + public void Addv_V_16BB_8HH_4SS([ValueSource("_8B4H2S_")] [Random(1)] ulong A0, + [ValueSource("_8B4H2S_")] [Random(1)] ulong A1, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B, 8H, 4S> + { + uint Opcode = 0x4E31B820; // ADDV B0, V1.16B + 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 = A0, X1 = A1 }; + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + + AArch64.Vpart(0, 0, new Bits(TestContext.CurrentContext.Random.NextULong())); + AArch64.Vpart(1, 0, new Bits(A0)); + AArch64.Vpart(1, 1, new Bits(A1)); + SimdFp.Addv_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); + }); + } + + [Test, Description("NEG , ")] + public void Neg_S_D([ValueSource("_1D_")] [Random(1)] ulong A) + { + uint Opcode = 0x7EE0B820; // NEG D0, D1 + Bits Op = new Bits(Opcode); + + AVec V0 = new AVec { X1 = TestContext.CurrentContext.Random.NextULong() }; + AVec V1 = new AVec { X0 = A }; + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + + AArch64.V(1, new Bits(A)); + 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); + } + + [Test, Description("NEG ., .")] + public void Neg_V_8B_4H_2S([ValueSource("_8B4H2S_")] [Random(1)] ulong A, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint Opcode = 0x2E20B820; // NEG V0.8B, V1.8B + Opcode |= ((size & 3) << 22); + Bits Op = new Bits(Opcode); + + AVec V0 = new AVec { X1 = TestContext.CurrentContext.Random.NextULong() }; + AVec V1 = new AVec { X0 = A }; + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + + AArch64.V(1, new Bits(A)); + 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); + } + + [Test, Pairwise, Description("NEG ., .")] + public void Neg_V_16B_8H_4S_2D([ValueSource("_8B4H2S1D_")] [Random(1)] ulong A0, + [ValueSource("_8B4H2S1D_")] [Random(1)] ulong A1, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint Opcode = 0x6E20B820; // NEG V0.16B, V1.16B + Opcode |= ((size & 3) << 22); + Bits Op = new Bits(Opcode); + + AVec V1 = new AVec { X0 = A0, X1 = A1 }; + AThreadState ThreadState = SingleOpcode(Opcode, V1: V1); + + AArch64.Vpart(1, 0, new Bits(A0)); + AArch64.Vpart(1, 1, new Bits(A1)); + SimdFp.Neg_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); + + Assert.Multiple(() => + { + Assert.That(ThreadState.V0.X0, Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); + Assert.That(ThreadState.V0.X1, Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); + }); + } +#endif + } +} diff --git a/Ryujinx.Tests/Cpu/CpuTestSimdArithmetic.cs b/Ryujinx.Tests/Cpu/CpuTestSimdArithmetic.cs index 56aaef488..f32fe398d 100644 --- a/Ryujinx.Tests/Cpu/CpuTestSimdArithmetic.cs +++ b/Ryujinx.Tests/Cpu/CpuTestSimdArithmetic.cs @@ -5,6 +5,583 @@ namespace Ryujinx.Tests.Cpu { public class CpuTestSimdArithmetic : CpuTest { + [TestCase(0xE228420u, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul)] + [TestCase(0xE228420u, 0x00000000FFFFFFFFul, 0x00000000FFFFFFFFul, 0x0000000000000001ul, 0x0000000000000001ul, 0x00000000FFFFFF00ul, 0x0000000000000000ul)] + [TestCase(0xE228420u, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFFFFFFFFFFul, 0xFEFEFEFEFEFEFEFEul, 0x0000000000000000ul)] + [TestCase(0xE228420u, 0x0102030405060708ul, 0xAAAAAAAAAAAAAAAAul, 0x0807060504030201ul, 0x2222222222222222ul, 0x0909090909090909ul, 0x0000000000000000ul)] + [TestCase(0x4E228420u, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul)] + [TestCase(0x4E228420u, 0x00000000FFFFFFFFul, 0x00000000FFFFFFFFul, 0x0000000000000001ul, 0x0000000000000001ul, 0x00000000FFFFFF00ul, 0x00000000FFFFFF00ul)] + [TestCase(0x4E228420u, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFFFFFFFFFFul, 0xFEFEFEFEFEFEFEFEul, 0xFEFEFEFEFEFEFEFEul)] + [TestCase(0x4E228420u, 0x0102030405060708ul, 0xAAAAAAAAAAAAAAAAul, 0x0807060504030201ul, 0x2222222222222222ul, 0x0909090909090909ul, 0xCCCCCCCCCCCCCCCCul)] + [TestCase(0xE628420u, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul)] + [TestCase(0xE628420u, 0x00000000FFFFFFFFul, 0x00000000FFFFFFFFul, 0x0000000000000001ul, 0x0000000000000001ul, 0x00000000FFFF0000ul, 0x0000000000000000ul)] + [TestCase(0xE628420u, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFFFFFFFFFFul, 0xFFFEFFFEFFFEFFFEul, 0x0000000000000000ul)] + [TestCase(0xE628420u, 0x0102030405060708ul, 0xAAAAAAAAAAAAAAAAul, 0x0807060504030201ul, 0x2222222222222222ul, 0x0909090909090909ul, 0x0000000000000000ul)] + [TestCase(0x4E628420u, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul)] + [TestCase(0x4E628420u, 0x00000000FFFFFFFFul, 0x00000000FFFFFFFFul, 0x0000000000000001ul, 0x0000000000000001ul, 0x00000000FFFF0000ul, 0x00000000FFFF0000ul)] + [TestCase(0x4E628420u, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFFFFFFFFFFul, 0xFFFEFFFEFFFEFFFEul, 0xFFFEFFFEFFFEFFFEul)] + [TestCase(0x4E628420u, 0x0102030405060708ul, 0xAAAAAAAAAAAAAAAAul, 0x0807060504030201ul, 0x2222222222222222ul, 0x0909090909090909ul, 0xCCCCCCCCCCCCCCCCul)] + [TestCase(0xEA28420u, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul)] + [TestCase(0xEA28420u, 0x00000000FFFFFFFFul, 0x00000000FFFFFFFFul, 0x0000000000000001ul, 0x0000000000000001ul, 0x0000000000000000ul, 0x0000000000000000ul)] + [TestCase(0xEA28420u, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFEFFFFFFFEul, 0x0000000000000000ul)] + [TestCase(0xEA28420u, 0x0102030405060708ul, 0xAAAAAAAAAAAAAAAAul, 0x0807060504030201ul, 0x2222222222222222ul, 0x0909090909090909ul, 0x0000000000000000ul)] + [TestCase(0x4EA28420u, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul)] + [TestCase(0x4EA28420u, 0x00000000FFFFFFFFul, 0x00000000FFFFFFFFul, 0x0000000000000001ul, 0x0000000000000001ul, 0x0000000000000000ul, 0x0000000000000000ul)] + [TestCase(0x4EA28420u, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFEFFFFFFFEul, 0xFFFFFFFEFFFFFFFEul)] + [TestCase(0x4EA28420u, 0x0102030405060708ul, 0xAAAAAAAAAAAAAAAAul, 0x0807060504030201ul, 0x2222222222222222ul, 0x0909090909090909ul, 0xCCCCCCCCCCCCCCCCul)] + [TestCase(0x4EE28420u, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul)] + [TestCase(0x4EE28420u, 0x00000000FFFFFFFFul, 0x00000000FFFFFFFFul, 0x0000000000000001ul, 0x0000000000000001ul, 0x0000000100000000ul, 0x0000000100000000ul)] + [TestCase(0x4EE28420u, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFFFFFFFFFEul, 0xFFFFFFFFFFFFFFFEul)] + [TestCase(0x4EE28420u, 0x0102030405060708ul, 0xAAAAAAAAAAAAAAAAul, 0x0807060504030201ul, 0x2222222222222222ul, 0x0909090909090909ul, 0xCCCCCCCCCCCCCCCCul)] + public void Add_V(uint Opcode, ulong A0, ulong A1, ulong B0, ulong B1, ulong Result0, ulong Result1) + { + AVec V1 = new AVec { X0 = A0, X1 = A1 }; + AVec V2 = new AVec { X0 = B0, X1 = B1 }; + AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, V2: V2); + Assert.Multiple(() => + { + Assert.AreEqual(Result0, ThreadState.V0.X0); + Assert.AreEqual(Result1, ThreadState.V0.X1); + }); + } + + [TestCase(0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u)] + [TestCase(0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u)] + [TestCase(0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u)] + [TestCase(0x80000000u, 0x80000000u, 0x3DCCCCCDu, 0x3DCCCCCDu, 0x3DCCCCCDu, 0x3DCCCCCDu)] + [TestCase(0x3DCCCCCDu, 0x3DCCCCCDu, 0x3C9623B1u, 0x3C9623B1u, 0x3DCCCCCDu, 0x3DCCCCCDu)] + [TestCase(0x8BA98D27u, 0x8BA98D27u, 0x00000076u, 0x00000076u, 0x00000076u, 0x00000076u)] + [TestCase(0x807FFFFFu, 0x807FFFFFu, 0x7F7FFFFFu, 0x7F7FFFFFu, 0x7F7FFFFFu, 0x7F7FFFFFu)] + [TestCase(0x7F7FFFFFu, 0x7F7FFFFFu, 0x807FFFFFu, 0x807FFFFFu, 0x7F7FFFFFu, 0x7F7FFFFFu)] + [TestCase(0x7FC00000u, 0x7FC00000u, 0x3F800000u, 0x3F800000u, 0x7FC00000u, 0x7FC00000u)] + [TestCase(0x3F800000u, 0x3F800000u, 0x7FC00000u, 0x7FC00000u, 0x7FC00000u, 0x7FC00000u)] + [TestCase(0x7F800001u, 0x7F800001u, 0x7FC00042u, 0x7FC00042u, 0x7FC00001u, 0x7FC00001u, Ignore = "NaN test.")] + [TestCase(0x7FC00042u, 0x7FC00042u, 0x7F800001u, 0x7F800001u, 0x7FC00001u, 0x7FC00001u, Ignore = "NaN test.")] + [TestCase(0x7FC0000Au, 0x7FC0000Au, 0x7FC0000Bu, 0x7FC0000Bu, 0x7FC0000Au, 0x7FC0000Au, Ignore = "NaN test.")] + public void Fmax_V(uint A, uint B, uint C, uint D, uint Result0, uint Result1) + { + uint Opcode = 0x4E22F420; + AVec V1 = new AVec { X0 = A, X1 = B }; + AVec V2 = new AVec { X0 = C, X1 = D }; + AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, V2: V2); + Assert.Multiple(() => + { + Assert.AreEqual(Result0, ThreadState.V0.X0); + Assert.AreEqual(Result1, ThreadState.V0.X1); + }); + } + + [TestCase(0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u)] + [TestCase(0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u)] + [TestCase(0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u)] + [TestCase(0x80000000u, 0x80000000u, 0x3DCCCCCDu, 0x3DCCCCCDu, 0x80000000u, 0x80000000u)] + [TestCase(0x3DCCCCCDu, 0x3DCCCCCDu, 0x3C9623B1u, 0x3C9623B1u, 0x3C9623B1u, 0x3C9623B1u)] + [TestCase(0x8BA98D27u, 0x8BA98D27u, 0x00000076u, 0x00000076u, 0x8BA98D27u, 0x8BA98D27u)] + [TestCase(0x807FFFFFu, 0x807FFFFFu, 0x7F7FFFFFu, 0x7F7FFFFFu, 0x807FFFFFu, 0x807FFFFFu)] + [TestCase(0x7F7FFFFFu, 0x7F7FFFFFu, 0x807FFFFFu, 0x807FFFFFu, 0x807FFFFFu, 0x807FFFFFu)] + [TestCase(0x7FC00000u, 0x7FC00000u, 0x3F800000u, 0x3F800000u, 0x7FC00000u, 0x7FC00000u)] + [TestCase(0x3F800000u, 0x3F800000u, 0x7FC00000u, 0x7FC00000u, 0x7FC00000u, 0x7FC00000u)] + [TestCase(0x7F800001u, 0x7F800001u, 0x7FC00042u, 0x7FC00042u, 0x7FC00001u, 0x7FC00001u, Ignore = "NaN test.")] + [TestCase(0x7FC00042u, 0x7FC00042u, 0x7F800001u, 0x7F800001u, 0x7FC00001u, 0x7FC00001u, Ignore = "NaN test.")] + [TestCase(0x7FC0000Au, 0x7FC0000Au, 0x7FC0000Bu, 0x7FC0000Bu, 0x7FC0000Au, 0x7FC0000Au, Ignore = "NaN test.")] + public void Fmin_V(uint A, uint B, uint C, uint D, uint Result0, uint Result1) + { + uint Opcode = 0x4EA2F420; + AVec V1 = new AVec { X0 = A, X1 = B }; + AVec V2 = new AVec { X0 = C, X1 = D }; + AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, V2: V2); + Assert.Multiple(() => + { + Assert.AreEqual(Result0, ThreadState.V0.X0); + Assert.AreEqual(Result1, ThreadState.V0.X1); + }); + } + + [Test, Description("fmul s6, s1, v0.s[2]")] + public void Fmul_Se([Random(10)] float A, [Random(10)] float B) + { + AThreadState ThreadState = SingleOpcode(0x5F809826, V1: new AVec { S0 = A }, V0: new AVec { S2 = B }); + + Assert.That(ThreadState.V6.S0, Is.EqualTo(A * B)); + } + + [Test, Description("frecpe v2.4s, v0.4s")] + public void Frecpe_V([Random(100)] float A) + { + AThreadState ThreadState = SingleOpcode(0x4EA1D802, V0: new AVec { S0 = A, S1 = A, S2 = A, S3 = A }); + + Assert.That(ThreadState.V2.S0, Is.EqualTo(1 / A)); + Assert.That(ThreadState.V2.S1, Is.EqualTo(1 / A)); + Assert.That(ThreadState.V2.S2, Is.EqualTo(1 / A)); + Assert.That(ThreadState.V2.S3, Is.EqualTo(1 / A)); + } + + [Test, Description("frecpe d0, d1")] + public void Frecpe_S([Random(100)] double A) + { + AThreadState ThreadState = SingleOpcode(0x5EE1D820, V1: new AVec { D0 = A }); + + Assert.That(ThreadState.V0.D0, Is.EqualTo(1 / A)); + } + + [Test, Description("frecps v4.4s, v2.4s, v0.4s")] + public void Frecps_V([Random(10)] float A, [Random(10)] float B) + { + AThreadState ThreadState = SingleOpcode(0x4E20FC44, V2: new AVec { S0 = A, S1 = A, S2 = A, S3 = A }, + V0: new AVec { S0 = B, S1 = B, S2 = B, S3 = B }); + + Assert.That(ThreadState.V4.S0, Is.EqualTo(2 - (A * B))); + Assert.That(ThreadState.V4.S1, Is.EqualTo(2 - (A * B))); + Assert.That(ThreadState.V4.S2, Is.EqualTo(2 - (A * B))); + Assert.That(ThreadState.V4.S3, Is.EqualTo(2 - (A * B))); + } + + [Test, Description("frecps d0, d1, d2")] + public void Frecps_S([Random(10)] double A, [Random(10)] double B) + { + AThreadState ThreadState = SingleOpcode(0x5E62FC20, V1: new AVec { D0 = A }, V2: new AVec { D0 = B }); + + Assert.That(ThreadState.V0.D0, Is.EqualTo(2 - (A * B))); + } + + [TestCase(0x3FE66666u, false, 0x40000000u)] + [TestCase(0x3F99999Au, false, 0x3F800000u)] + [TestCase(0x404CCCCDu, false, 0x40400000u)] + [TestCase(0x40733333u, false, 0x40800000u)] + [TestCase(0x3fc00000u, false, 0x40000000u)] + [TestCase(0x40200000u, false, 0x40400000u)] + [TestCase(0x00000000u, false, 0x00000000u)] + [TestCase(0x00000000u, false, 0x00000000u)] + [TestCase(0x00000000u, false, 0x00000000u)] + [TestCase(0x00000000u, false, 0x00000000u)] + [TestCase(0x80000000u, false, 0x80000000u)] + [TestCase(0x80000000u, false, 0x80000000u)] + [TestCase(0x80000000u, false, 0x80000000u)] + [TestCase(0x80000000u, false, 0x80000000u)] + [TestCase(0x7F800000u, false, 0x7F800000u)] + [TestCase(0x7F800000u, false, 0x7F800000u)] + [TestCase(0x7F800000u, false, 0x7F800000u)] + [TestCase(0x7F800000u, false, 0x7F800000u)] + [TestCase(0xFF800000u, false, 0xFF800000u)] + [TestCase(0xFF800000u, false, 0xFF800000u)] + [TestCase(0xFF800000u, false, 0xFF800000u)] + [TestCase(0xFF800000u, false, 0xFF800000u)] + [TestCase(0xFF800001u, false, 0xFFC00001u, Ignore = "NaN test.")] + [TestCase(0xFF800001u, false, 0xFFC00001u, Ignore = "NaN test.")] + [TestCase(0xFF800001u, false, 0xFFC00001u, Ignore = "NaN test.")] + [TestCase(0xFF800001u, false, 0xFFC00001u, Ignore = "NaN test.")] + [TestCase(0xFF800001u, true, 0x7FC00000u, Ignore = "NaN test.")] + [TestCase(0xFF800001u, true, 0x7FC00000u, Ignore = "NaN test.")] + [TestCase(0xFF800001u, true, 0x7FC00000u, Ignore = "NaN test.")] + [TestCase(0xFF800001u, true, 0x7FC00000u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, false, 0x7FC00002u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, false, 0x7FC00002u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, false, 0x7FC00002u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, false, 0x7FC00002u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, true, 0x7FC00000u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, true, 0x7FC00000u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, true, 0x7FC00000u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, true, 0x7FC00000u, Ignore = "NaN test.")] + public void Frinta_S(uint A, bool DefaultNaN, uint Result) + { + int FpcrTemp = 0x0; + if(DefaultNaN) + { + FpcrTemp = 0x2000000; + } + AVec V1 = new AVec { X0 = A }; + AThreadState ThreadState = SingleOpcode(0x1E264020, V1: V1, Fpcr: FpcrTemp); + Assert.AreEqual(Result, ThreadState.V0.X0); + } + + [TestCase(0x6E618820u, 0x3FF3333333333333ul, 0x3FF3333333333333ul, false, 0x3FF0000000000000ul, 0x3FF0000000000000ul)] + [TestCase(0x6E618820u, 0x3FFCCCCCCCCCCCCDul, 0x3FFCCCCCCCCCCCCDul, false, 0x4000000000000000ul, 0x4000000000000000ul)] + [TestCase(0x6E618820u, 0x3FF8000000000000ul, 0x3FF8000000000000ul, false, 0x4000000000000000ul, 0x4000000000000000ul)] + [TestCase(0x6E219820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, false, 0x3f80000040000000ul, 0x3f80000040000000ul)] + [TestCase(0x6E219820u, 0x3fc000003fc00000ul, 0x3fc000003fc00000ul, false, 0x4000000040000000ul, 0x4000000040000000ul)] + [TestCase(0x2E219820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, false, 0x3f80000040000000ul, 0x0000000000000000ul)] + [TestCase(0x2E219820u, 0x3fc000003fc00000ul, 0x3fc000003fc00000ul, false, 0x4000000040000000ul, 0x0000000000000000ul)] + [TestCase(0x2E218820u, 0x0000000080000000ul, 0x0000000000000000ul, false, 0x0000000080000000ul, 0x0000000000000000ul)] + [TestCase(0x2E218820u, 0x7F800000FF800000ul, 0x0000000000000000ul, false, 0x7F800000FF800000ul, 0x0000000000000000ul)] + [TestCase(0x2E218820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, false, 0xFFC000017FC00002ul, 0x0000000000000000ul, Ignore = "NaN test.")] + [TestCase(0x2E218820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, true, 0x7FC000007FC00000ul, 0x0000000000000000ul, Ignore = "NaN test.")] + public void Frinta_V(uint Opcode, ulong A, ulong B, bool DefaultNaN, ulong Result0, ulong Result1) + { + int FpcrTemp = 0x0; + if(DefaultNaN) + { + FpcrTemp = 0x2000000; + } + AVec V1 = new AVec { X0 = A, X1 = B }; + AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, Fpcr: FpcrTemp); + Assert.Multiple(() => + { + Assert.AreEqual(Result0, ThreadState.V0.X0); + Assert.AreEqual(Result1, ThreadState.V0.X1); + }); + } + + [TestCase(0x3FE66666u, 'N', false, 0x40000000u)] + [TestCase(0x3F99999Au, 'N', false, 0x3F800000u)] + [TestCase(0x404CCCCDu, 'P', false, 0x40800000u)] + [TestCase(0x40733333u, 'P', false, 0x40800000u)] + [TestCase(0x404CCCCDu, 'M', false, 0x40400000u)] + [TestCase(0x40733333u, 'M', false, 0x40400000u)] + [TestCase(0x3F99999Au, 'Z', false, 0x3F800000u)] + [TestCase(0x3FE66666u, 'Z', false, 0x3F800000u)] + [TestCase(0x00000000u, 'N', false, 0x00000000u)] + [TestCase(0x00000000u, 'P', false, 0x00000000u)] + [TestCase(0x00000000u, 'M', false, 0x00000000u)] + [TestCase(0x00000000u, 'Z', false, 0x00000000u)] + [TestCase(0x80000000u, 'N', false, 0x80000000u)] + [TestCase(0x80000000u, 'P', false, 0x80000000u)] + [TestCase(0x80000000u, 'M', false, 0x80000000u)] + [TestCase(0x80000000u, 'Z', false, 0x80000000u)] + [TestCase(0x7F800000u, 'N', false, 0x7F800000u)] + [TestCase(0x7F800000u, 'P', false, 0x7F800000u)] + [TestCase(0x7F800000u, 'M', false, 0x7F800000u)] + [TestCase(0x7F800000u, 'Z', false, 0x7F800000u)] + [TestCase(0xFF800000u, 'N', false, 0xFF800000u)] + [TestCase(0xFF800000u, 'P', false, 0xFF800000u)] + [TestCase(0xFF800000u, 'M', false, 0xFF800000u)] + [TestCase(0xFF800000u, 'Z', false, 0xFF800000u)] + [TestCase(0xFF800001u, 'N', false, 0xFFC00001u, Ignore = "NaN test.")] + [TestCase(0xFF800001u, 'P', false, 0xFFC00001u, Ignore = "NaN test.")] + [TestCase(0xFF800001u, 'M', false, 0xFFC00001u, Ignore = "NaN test.")] + [TestCase(0xFF800001u, 'Z', false, 0xFFC00001u, Ignore = "NaN test.")] + [TestCase(0xFF800001u, 'N', true, 0x7FC00000u, Ignore = "NaN test.")] + [TestCase(0xFF800001u, 'P', true, 0x7FC00000u, Ignore = "NaN test.")] + [TestCase(0xFF800001u, 'M', true, 0x7FC00000u, Ignore = "NaN test.")] + [TestCase(0xFF800001u, 'Z', true, 0x7FC00000u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, 'N', false, 0x7FC00002u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, 'P', false, 0x7FC00002u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, 'M', false, 0x7FC00002u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, 'Z', false, 0x7FC00002u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, 'N', true, 0x7FC00000u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, 'P', true, 0x7FC00000u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, 'M', true, 0x7FC00000u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, 'Z', true, 0x7FC00000u, Ignore = "NaN test.")] + public void Frinti_S(uint A, char RoundType, bool DefaultNaN, uint Result) + { + int FpcrTemp = 0x0; + switch(RoundType) + { + case 'N': + FpcrTemp = 0x0; + break; + + case 'P': + FpcrTemp = 0x400000; + break; + + case 'M': + FpcrTemp = 0x800000; + break; + + case 'Z': + FpcrTemp = 0xC00000; + break; + } + if(DefaultNaN) + { + FpcrTemp |= 1 << 25; + } + AVec V1 = new AVec { X0 = A }; + AThreadState ThreadState = SingleOpcode(0x1E27C020, V1: V1, Fpcr: FpcrTemp); + Assert.AreEqual(Result, ThreadState.V0.X0); + } + + [TestCase(0x6EE19820u, 0x3FF3333333333333ul, 0x3FF3333333333333ul, 'N', false, 0x3FF0000000000000ul, 0x3FF0000000000000ul)] + [TestCase(0x6EE19820u, 0x3FFCCCCCCCCCCCCDul, 0x3FFCCCCCCCCCCCCDul, 'N', false, 0x4000000000000000ul, 0x4000000000000000ul)] + [TestCase(0x6EE19820u, 0x3FF3333333333333ul, 0x3FF3333333333333ul, 'P', false, 0x4000000000000000ul, 0x4000000000000000ul)] + [TestCase(0x6EE19820u, 0x3FFCCCCCCCCCCCCDul, 0x3FFCCCCCCCCCCCCDul, 'P', false, 0x4000000000000000ul, 0x4000000000000000ul)] + [TestCase(0x6EE19820u, 0x3FF3333333333333ul, 0x3FF3333333333333ul, 'M', false, 0x3FF0000000000000ul, 0x3FF0000000000000ul)] + [TestCase(0x6EE19820u, 0x3FFCCCCCCCCCCCCDul, 0x3FFCCCCCCCCCCCCDul, 'M', false, 0x3FF0000000000000ul, 0x3FF0000000000000ul)] + [TestCase(0x6EE19820u, 0x3FF3333333333333ul, 0x3FF3333333333333ul, 'Z', false, 0x3FF0000000000000ul, 0x3FF0000000000000ul)] + [TestCase(0x6EE19820u, 0x3FFCCCCCCCCCCCCDul, 0x3FFCCCCCCCCCCCCDul, 'Z', false, 0x3FF0000000000000ul, 0x3FF0000000000000ul)] + [TestCase(0x6EA19820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, 'N', false, 0x3f80000040000000ul, 0x3f80000040000000ul)] + [TestCase(0x6EA19820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, 'P', false, 0x4000000040000000ul, 0x4000000040000000ul)] + [TestCase(0x6EA19820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, 'M', false, 0x3f8000003f800000ul, 0x3f8000003f800000ul)] + [TestCase(0x6EA19820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, 'Z', false, 0x3f8000003f800000ul, 0x3f8000003f800000ul)] + [TestCase(0x2EA19820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, 'N', false, 0x3f80000040000000ul, 0x0000000000000000ul)] + [TestCase(0x2EA19820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, 'P', false, 0x4000000040000000ul, 0x0000000000000000ul)] + [TestCase(0x2EA19820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, 'M', false, 0x3f8000003f800000ul, 0x0000000000000000ul)] + [TestCase(0x2EA19820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, 'Z', false, 0x3f8000003f800000ul, 0x0000000000000000ul)] + [TestCase(0x2EA19820u, 0x0000000080000000ul, 0x0000000000000000ul, 'N', false, 0x0000000080000000ul, 0x0000000000000000ul)] + [TestCase(0x2EA19820u, 0x0000000080000000ul, 0x0000000000000000ul, 'P', false, 0x0000000080000000ul, 0x0000000000000000ul)] + [TestCase(0x2EA19820u, 0x0000000080000000ul, 0x0000000000000000ul, 'M', false, 0x0000000080000000ul, 0x0000000000000000ul)] + [TestCase(0x2EA19820u, 0x0000000080000000ul, 0x0000000000000000ul, 'Z', false, 0x0000000080000000ul, 0x0000000000000000ul)] + [TestCase(0x2EA19820u, 0x7F800000FF800000ul, 0x0000000000000000ul, 'N', false, 0x7F800000FF800000ul, 0x0000000000000000ul)] + [TestCase(0x2EA19820u, 0x7F800000FF800000ul, 0x0000000000000000ul, 'P', false, 0x7F800000FF800000ul, 0x0000000000000000ul)] + [TestCase(0x2EA19820u, 0x7F800000FF800000ul, 0x0000000000000000ul, 'M', false, 0x7F800000FF800000ul, 0x0000000000000000ul)] + [TestCase(0x2EA19820u, 0x7F800000FF800000ul, 0x0000000000000000ul, 'Z', false, 0x7F800000FF800000ul, 0x0000000000000000ul)] + [TestCase(0x2EA19820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, 'N', false, 0xFFC000017FC00002ul, 0x0000000000000000ul, Ignore = "NaN test.")] + [TestCase(0x2EA19820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, 'P', false, 0xFFC000017FC00002ul, 0x0000000000000000ul, Ignore = "NaN test.")] + [TestCase(0x2EA19820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, 'M', false, 0xFFC000017FC00002ul, 0x0000000000000000ul, Ignore = "NaN test.")] + [TestCase(0x2EA19820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, 'Z', false, 0xFFC000017FC00002ul, 0x0000000000000000ul, Ignore = "NaN test.")] + [TestCase(0x2EA19820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, 'N', true, 0x7FC000007FC00000ul, 0x0000000000000000ul, Ignore = "NaN test.")] + [TestCase(0x2EA19820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, 'P', true, 0x7FC000007FC00000ul, 0x0000000000000000ul, Ignore = "NaN test.")] + [TestCase(0x2EA19820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, 'M', true, 0x7FC000007FC00000ul, 0x0000000000000000ul, Ignore = "NaN test.")] + [TestCase(0x2EA19820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, 'Z', true, 0x7FC000007FC00000ul, 0x0000000000000000ul, Ignore = "NaN test.")] + public void Frinti_V(uint Opcode, ulong A, ulong B, char RoundType, bool DefaultNaN, ulong Result0, ulong Result1) + { + int FpcrTemp = 0x0; + switch(RoundType) + { + case 'N': + FpcrTemp = 0x0; + break; + + case 'P': + FpcrTemp = 0x400000; + break; + + case 'M': + FpcrTemp = 0x800000; + break; + + case 'Z': + FpcrTemp = 0xC00000; + break; + } + if(DefaultNaN) + { + FpcrTemp |= 1 << 25; + } + AVec V1 = new AVec { X0 = A, X1 = B }; + AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, Fpcr: FpcrTemp); + Assert.Multiple(() => + { + Assert.AreEqual(Result0, ThreadState.V0.X0); + Assert.AreEqual(Result1, ThreadState.V0.X1); + }); + } + + [TestCase(0x3FE66666u, false, 0x3F800000u)] + [TestCase(0x3F99999Au, false, 0x3F800000u)] + [TestCase(0x404CCCCDu, false, 0x40400000u)] + [TestCase(0x40733333u, false, 0x40400000u)] + [TestCase(0x3fc00000u, false, 0x3F800000u)] + [TestCase(0x40200000u, false, 0x40000000u)] + [TestCase(0x00000000u, false, 0x00000000u)] + [TestCase(0x00000000u, false, 0x00000000u)] + [TestCase(0x00000000u, false, 0x00000000u)] + [TestCase(0x00000000u, false, 0x00000000u)] + [TestCase(0x80000000u, false, 0x80000000u)] + [TestCase(0x80000000u, false, 0x80000000u)] + [TestCase(0x80000000u, false, 0x80000000u)] + [TestCase(0x80000000u, false, 0x80000000u)] + [TestCase(0x7F800000u, false, 0x7F800000u)] + [TestCase(0x7F800000u, false, 0x7F800000u)] + [TestCase(0x7F800000u, false, 0x7F800000u)] + [TestCase(0x7F800000u, false, 0x7F800000u)] + [TestCase(0xFF800000u, false, 0xFF800000u)] + [TestCase(0xFF800000u, false, 0xFF800000u)] + [TestCase(0xFF800000u, false, 0xFF800000u)] + [TestCase(0xFF800000u, false, 0xFF800000u)] + [TestCase(0xFF800001u, false, 0xFFC00001u, Ignore = "NaN test.")] + [TestCase(0xFF800001u, false, 0xFFC00001u, Ignore = "NaN test.")] + [TestCase(0xFF800001u, false, 0xFFC00001u, Ignore = "NaN test.")] + [TestCase(0xFF800001u, false, 0xFFC00001u, Ignore = "NaN test.")] + [TestCase(0xFF800001u, true, 0x7FC00000u, Ignore = "NaN test.")] + [TestCase(0xFF800001u, true, 0x7FC00000u, Ignore = "NaN test.")] + [TestCase(0xFF800001u, true, 0x7FC00000u, Ignore = "NaN test.")] + [TestCase(0xFF800001u, true, 0x7FC00000u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, false, 0x7FC00002u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, false, 0x7FC00002u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, false, 0x7FC00002u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, false, 0x7FC00002u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, true, 0x7FC00000u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, true, 0x7FC00000u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, true, 0x7FC00000u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, true, 0x7FC00000u, Ignore = "NaN test.")] + public void Frintm_S(uint A, bool DefaultNaN, uint Result) + { + int FpcrTemp = 0x0; + if(DefaultNaN) + { + FpcrTemp = 0x2000000; + } + AVec V1 = new AVec { X0 = A }; + AThreadState ThreadState = SingleOpcode(0x1E254020, V1: V1, Fpcr: FpcrTemp); + Assert.AreEqual(Result, ThreadState.V0.X0); + } + + [TestCase(0x4E619820u, 0x3FF3333333333333ul, 0x3FF3333333333333ul, false, 0x3FF0000000000000ul, 0x3FF0000000000000ul)] + [TestCase(0x4E619820u, 0x3FFCCCCCCCCCCCCDul, 0x3FFCCCCCCCCCCCCDul, false, 0x3FF0000000000000ul, 0x3FF0000000000000ul)] + [TestCase(0x4E219820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, false, 0x3f8000003f800000ul, 0x3f8000003f800000ul)] + [TestCase(0xE219820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, false, 0x3f8000003f800000ul, 0x0000000000000000ul)] + [TestCase(0xE219820u, 0x0000000080000000ul, 0x0000000000000000ul, false, 0x0000000080000000ul, 0x0000000000000000ul)] + [TestCase(0xE219820u, 0x7F800000FF800000ul, 0x0000000000000000ul, false, 0x7F800000FF800000ul, 0x0000000000000000ul)] + [TestCase(0xE219820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, false, 0xFFC000017FC00002ul, 0x0000000000000000ul, Ignore = "NaN test.")] + [TestCase(0xE219820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, true, 0x7FC000007FC00000ul, 0x0000000000000000ul, Ignore = "NaN test.")] + public void Frintm_V(uint Opcode, ulong A, ulong B, bool DefaultNaN, ulong Result0, ulong Result1) + { + int FpcrTemp = 0x0; + if(DefaultNaN) + { + FpcrTemp = 0x2000000; + } + AVec V1 = new AVec { X0 = A, X1 = B }; + AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, Fpcr: FpcrTemp); + Assert.Multiple(() => + { + Assert.AreEqual(Result0, ThreadState.V0.X0); + Assert.AreEqual(Result1, ThreadState.V0.X1); + }); + } + + [TestCase(0x3FE66666u, false, 0x40000000u)] + [TestCase(0x3F99999Au, false, 0x3F800000u)] + [TestCase(0x404CCCCDu, false, 0x40400000u)] + [TestCase(0x40733333u, false, 0x40800000u)] + [TestCase(0x3fc00000u, false, 0x40000000u)] + [TestCase(0x40200000u, false, 0x40400000u)] + [TestCase(0x00000000u, false, 0x00000000u)] + [TestCase(0x00000000u, false, 0x00000000u)] + [TestCase(0x00000000u, false, 0x00000000u)] + [TestCase(0x00000000u, false, 0x00000000u)] + [TestCase(0x80000000u, false, 0x80000000u)] + [TestCase(0x80000000u, false, 0x80000000u)] + [TestCase(0x80000000u, false, 0x80000000u)] + [TestCase(0x80000000u, false, 0x80000000u)] + [TestCase(0x7F800000u, false, 0x7F800000u)] + [TestCase(0x7F800000u, false, 0x7F800000u)] + [TestCase(0x7F800000u, false, 0x7F800000u)] + [TestCase(0x7F800000u, false, 0x7F800000u)] + [TestCase(0xFF800000u, false, 0xFF800000u)] + [TestCase(0xFF800000u, false, 0xFF800000u)] + [TestCase(0xFF800000u, false, 0xFF800000u)] + [TestCase(0xFF800000u, false, 0xFF800000u)] + [TestCase(0xFF800001u, false, 0xFFC00001u, Ignore = "NaN test.")] + [TestCase(0xFF800001u, false, 0xFFC00001u, Ignore = "NaN test.")] + [TestCase(0xFF800001u, false, 0xFFC00001u, Ignore = "NaN test.")] + [TestCase(0xFF800001u, false, 0xFFC00001u, Ignore = "NaN test.")] + [TestCase(0xFF800001u, true, 0x7FC00000u, Ignore = "NaN test.")] + [TestCase(0xFF800001u, true, 0x7FC00000u, Ignore = "NaN test.")] + [TestCase(0xFF800001u, true, 0x7FC00000u, Ignore = "NaN test.")] + [TestCase(0xFF800001u, true, 0x7FC00000u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, false, 0x7FC00002u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, false, 0x7FC00002u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, false, 0x7FC00002u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, false, 0x7FC00002u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, true, 0x7FC00000u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, true, 0x7FC00000u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, true, 0x7FC00000u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, true, 0x7FC00000u, Ignore = "NaN test.")] + public void Frintn_S(uint A, bool DefaultNaN, uint Result) + { + int FpcrTemp = 0x0; + if(DefaultNaN) + { + FpcrTemp = 0x2000000; + } + AVec V1 = new AVec { X0 = A }; + AThreadState ThreadState = SingleOpcode(0x1E264020, V1: V1, Fpcr: FpcrTemp); + Assert.AreEqual(Result, ThreadState.V0.X0); + } + + [TestCase(0x4E618820u, 0x3FF3333333333333ul, 0x3FF3333333333333ul, false, 0x3FF0000000000000ul, 0x3FF0000000000000ul)] + [TestCase(0x4E618820u, 0x3FFCCCCCCCCCCCCDul, 0x3FFCCCCCCCCCCCCDul, false, 0x4000000000000000ul, 0x4000000000000000ul)] + [TestCase(0x4E618820u, 0x3FF8000000000000ul, 0x3FF8000000000000ul, false, 0x4000000000000000ul, 0x4000000000000000ul)] + [TestCase(0x4E218820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, false, 0x3f80000040000000ul, 0x3f80000040000000ul)] + [TestCase(0x4E218820u, 0x3fc000003fc00000ul, 0x3fc000003fc00000ul, false, 0x4000000040000000ul, 0x4000000040000000ul)] + [TestCase(0xE218820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, false, 0x3f80000040000000ul, 0x0000000000000000ul)] + [TestCase(0xE218820u, 0x3fc000003fc00000ul, 0x3fc000003fc00000ul, false, 0x4000000040000000ul, 0x0000000000000000ul)] + [TestCase(0xE218820u, 0x0000000080000000ul, 0x0000000000000000ul, false, 0x0000000080000000ul, 0x0000000000000000ul)] + [TestCase(0xE218820u, 0x7F800000FF800000ul, 0x0000000000000000ul, false, 0x7F800000FF800000ul, 0x0000000000000000ul)] + [TestCase(0xE218820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, false, 0xFFC000017FC00002ul, 0x0000000000000000ul, Ignore = "NaN test.")] + [TestCase(0xE218820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, true, 0x7FC000007FC00000ul, 0x0000000000000000ul, Ignore = "NaN test.")] + public void Frintn_V(uint Opcode, ulong A, ulong B, bool DefaultNaN, ulong Result0, ulong Result1) + { + int FpcrTemp = 0x0; + if(DefaultNaN) + { + FpcrTemp = 0x2000000; + } + AVec V1 = new AVec { X0 = A, X1 = B }; + AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, Fpcr: FpcrTemp); + Assert.Multiple(() => + { + Assert.AreEqual(Result0, ThreadState.V0.X0); + Assert.AreEqual(Result1, ThreadState.V0.X1); + }); + } + + [TestCase(0x3FE66666u, false, 0x40000000u)] + [TestCase(0x3F99999Au, false, 0x40000000u)] + [TestCase(0x404CCCCDu, false, 0x40800000u)] + [TestCase(0x40733333u, false, 0x40800000u)] + [TestCase(0x3fc00000u, false, 0x40000000u)] + [TestCase(0x40200000u, false, 0x40400000u)] + [TestCase(0x00000000u, false, 0x00000000u)] + [TestCase(0x00000000u, false, 0x00000000u)] + [TestCase(0x00000000u, false, 0x00000000u)] + [TestCase(0x00000000u, false, 0x00000000u)] + [TestCase(0x80000000u, false, 0x80000000u)] + [TestCase(0x80000000u, false, 0x80000000u)] + [TestCase(0x80000000u, false, 0x80000000u)] + [TestCase(0x80000000u, false, 0x80000000u)] + [TestCase(0x7F800000u, false, 0x7F800000u)] + [TestCase(0x7F800000u, false, 0x7F800000u)] + [TestCase(0x7F800000u, false, 0x7F800000u)] + [TestCase(0x7F800000u, false, 0x7F800000u)] + [TestCase(0xFF800000u, false, 0xFF800000u)] + [TestCase(0xFF800000u, false, 0xFF800000u)] + [TestCase(0xFF800000u, false, 0xFF800000u)] + [TestCase(0xFF800000u, false, 0xFF800000u)] + [TestCase(0xFF800001u, false, 0xFFC00001u, Ignore = "NaN test.")] + [TestCase(0xFF800001u, false, 0xFFC00001u, Ignore = "NaN test.")] + [TestCase(0xFF800001u, false, 0xFFC00001u, Ignore = "NaN test.")] + [TestCase(0xFF800001u, false, 0xFFC00001u, Ignore = "NaN test.")] + [TestCase(0xFF800001u, true, 0x7FC00000u, Ignore = "NaN test.")] + [TestCase(0xFF800001u, true, 0x7FC00000u, Ignore = "NaN test.")] + [TestCase(0xFF800001u, true, 0x7FC00000u, Ignore = "NaN test.")] + [TestCase(0xFF800001u, true, 0x7FC00000u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, false, 0x7FC00002u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, false, 0x7FC00002u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, false, 0x7FC00002u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, false, 0x7FC00002u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, true, 0x7FC00000u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, true, 0x7FC00000u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, true, 0x7FC00000u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, true, 0x7FC00000u, Ignore = "NaN test.")] + public void Frintp_S(uint A, bool DefaultNaN, uint Result) + { + int FpcrTemp = 0x0; + if(DefaultNaN) + { + FpcrTemp = 0x2000000; + } + AVec V1 = new AVec { X0 = A }; + AThreadState ThreadState = SingleOpcode(0x1E24C020, V1: V1, Fpcr: FpcrTemp); + Assert.AreEqual(Result, ThreadState.V0.X0); + } + + [TestCase(0x4EE18820u, 0x3FF3333333333333ul, 0x3FF3333333333333ul, false, 0x4000000000000000ul, 0x4000000000000000ul)] + [TestCase(0x4EE18820u, 0x3FFCCCCCCCCCCCCDul, 0x3FFCCCCCCCCCCCCDul, false, 0x4000000000000000ul, 0x4000000000000000ul)] + [TestCase(0x4EA18820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, false, 0x4000000040000000ul, 0x4000000040000000ul)] + [TestCase(0xEA18820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, false, 0x4000000040000000ul, 0x0000000000000000ul)] + [TestCase(0xEA18820u, 0x0000000080000000ul, 0x0000000000000000ul, false, 0x0000000080000000ul, 0x0000000000000000ul)] + [TestCase(0xEA18820u, 0x7F800000FF800000ul, 0x0000000000000000ul, false, 0x7F800000FF800000ul, 0x0000000000000000ul)] + [TestCase(0xEA18820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, false, 0xFFC000017FC00002ul, 0x0000000000000000ul, Ignore = "NaN test.")] + [TestCase(0xEA18820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, true, 0x7FC000007FC00000ul, 0x0000000000000000ul, Ignore = "NaN test.")] + public void Frintp_V(uint Opcode, ulong A, ulong B, bool DefaultNaN, ulong Result0, ulong Result1) + { + int FpcrTemp = 0x0; + if(DefaultNaN) + { + FpcrTemp = 0x2000000; + } + AVec V1 = new AVec { X0 = A, X1 = B }; + AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, Fpcr: FpcrTemp); + Assert.Multiple(() => + { + Assert.AreEqual(Result0, ThreadState.V0.X0); + Assert.AreEqual(Result1, ThreadState.V0.X1); + }); + } + [TestCase(0x3FE66666u, 'N', false, 0x40000000u)] [TestCase(0x3F99999Au, 'N', false, 0x3F800000u)] [TestCase(0x404CCCCDu, 'P', false, 0x40800000u)] @@ -29,22 +606,22 @@ namespace Ryujinx.Tests.Cpu [TestCase(0xFF800000u, 'P', false, 0xFF800000u)] [TestCase(0xFF800000u, 'M', false, 0xFF800000u)] [TestCase(0xFF800000u, 'Z', false, 0xFF800000u)] - [TestCase(0xFF800001u, 'N', false, 0xFFC00001u)] - [TestCase(0xFF800001u, 'P', false, 0xFFC00001u)] - [TestCase(0xFF800001u, 'M', false, 0xFFC00001u)] - [TestCase(0xFF800001u, 'Z', false, 0xFFC00001u)] - [TestCase(0xFF800001u, 'N', true, 0x7FC00000u)] - [TestCase(0xFF800001u, 'P', true, 0x7FC00000u)] - [TestCase(0xFF800001u, 'M', true, 0x7FC00000u)] - [TestCase(0xFF800001u, 'Z', true, 0x7FC00000u)] - [TestCase(0x7FC00002u, 'N', false, 0x7FC00002u)] - [TestCase(0x7FC00002u, 'P', false, 0x7FC00002u)] - [TestCase(0x7FC00002u, 'M', false, 0x7FC00002u)] - [TestCase(0x7FC00002u, 'Z', false, 0x7FC00002u)] - [TestCase(0x7FC00002u, 'N', true, 0x7FC00000u)] - [TestCase(0x7FC00002u, 'P', true, 0x7FC00000u)] - [TestCase(0x7FC00002u, 'M', true, 0x7FC00000u)] - [TestCase(0x7FC00002u, 'Z', true, 0x7FC00000u)] + [TestCase(0xFF800001u, 'N', false, 0xFFC00001u, Ignore = "NaN test.")] + [TestCase(0xFF800001u, 'P', false, 0xFFC00001u, Ignore = "NaN test.")] + [TestCase(0xFF800001u, 'M', false, 0xFFC00001u, Ignore = "NaN test.")] + [TestCase(0xFF800001u, 'Z', false, 0xFFC00001u, Ignore = "NaN test.")] + [TestCase(0xFF800001u, 'N', true, 0x7FC00000u, Ignore = "NaN test.")] + [TestCase(0xFF800001u, 'P', true, 0x7FC00000u, Ignore = "NaN test.")] + [TestCase(0xFF800001u, 'M', true, 0x7FC00000u, Ignore = "NaN test.")] + [TestCase(0xFF800001u, 'Z', true, 0x7FC00000u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, 'N', false, 0x7FC00002u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, 'P', false, 0x7FC00002u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, 'M', false, 0x7FC00002u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, 'Z', false, 0x7FC00002u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, 'N', true, 0x7FC00000u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, 'P', true, 0x7FC00000u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, 'M', true, 0x7FC00000u, Ignore = "NaN test.")] + [TestCase(0x7FC00002u, 'Z', true, 0x7FC00000u, Ignore = "NaN test.")] public void Frintx_S(uint A, char RoundType, bool DefaultNaN, uint Result) { int FpcrTemp = 0x0; @@ -74,5 +651,79 @@ namespace Ryujinx.Tests.Cpu AThreadState ThreadState = SingleOpcode(0x1E274020, V1: V1, Fpcr: FpcrTemp); Assert.AreEqual(Result, ThreadState.V0.X0); } + + [TestCase(0x6E619820u, 0x3FF3333333333333ul, 0x3FF3333333333333ul, 'N', false, 0x3FF0000000000000ul, 0x3FF0000000000000ul)] + [TestCase(0x6E619820u, 0x3FFCCCCCCCCCCCCDul, 0x3FFCCCCCCCCCCCCDul, 'N', false, 0x4000000000000000ul, 0x4000000000000000ul)] + [TestCase(0x6E619820u, 0x3FF3333333333333ul, 0x3FF3333333333333ul, 'P', false, 0x4000000000000000ul, 0x4000000000000000ul)] + [TestCase(0x6E619820u, 0x3FFCCCCCCCCCCCCDul, 0x3FFCCCCCCCCCCCCDul, 'P', false, 0x4000000000000000ul, 0x4000000000000000ul)] + [TestCase(0x6E619820u, 0x3FF3333333333333ul, 0x3FF3333333333333ul, 'M', false, 0x3FF0000000000000ul, 0x3FF0000000000000ul)] + [TestCase(0x6E619820u, 0x3FFCCCCCCCCCCCCDul, 0x3FFCCCCCCCCCCCCDul, 'M', false, 0x3FF0000000000000ul, 0x3FF0000000000000ul)] + [TestCase(0x6E619820u, 0x3FF3333333333333ul, 0x3FF3333333333333ul, 'Z', false, 0x3FF0000000000000ul, 0x3FF0000000000000ul)] + [TestCase(0x6E619820u, 0x3FFCCCCCCCCCCCCDul, 0x3FFCCCCCCCCCCCCDul, 'Z', false, 0x3FF0000000000000ul, 0x3FF0000000000000ul)] + [TestCase(0x6E219820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, 'N', false, 0x3f80000040000000ul, 0x3f80000040000000ul)] + [TestCase(0x6E219820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, 'P', false, 0x4000000040000000ul, 0x4000000040000000ul)] + [TestCase(0x6E219820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, 'M', false, 0x3f8000003f800000ul, 0x3f8000003f800000ul)] + [TestCase(0x6E219820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, 'Z', false, 0x3f8000003f800000ul, 0x3f8000003f800000ul)] + [TestCase(0x2E219820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, 'N', false, 0x3f80000040000000ul, 0x0000000000000000ul)] + [TestCase(0x2E219820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, 'P', false, 0x4000000040000000ul, 0x0000000000000000ul)] + [TestCase(0x2E219820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, 'M', false, 0x3f8000003f800000ul, 0x0000000000000000ul)] + [TestCase(0x2E219820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, 'Z', false, 0x3f8000003f800000ul, 0x0000000000000000ul)] + [TestCase(0x2E219820u, 0x0000000080000000ul, 0x0000000000000000ul, 'N', false, 0x0000000080000000ul, 0x0000000000000000ul)] + [TestCase(0x2E219820u, 0x0000000080000000ul, 0x0000000000000000ul, 'P', false, 0x0000000080000000ul, 0x0000000000000000ul)] + [TestCase(0x2E219820u, 0x0000000080000000ul, 0x0000000000000000ul, 'M', false, 0x0000000080000000ul, 0x0000000000000000ul)] + [TestCase(0x2E219820u, 0x0000000080000000ul, 0x0000000000000000ul, 'Z', false, 0x0000000080000000ul, 0x0000000000000000ul)] + [TestCase(0x2E219820u, 0x7F800000FF800000ul, 0x0000000000000000ul, 'N', false, 0x7F800000FF800000ul, 0x0000000000000000ul)] + [TestCase(0x2E219820u, 0x7F800000FF800000ul, 0x0000000000000000ul, 'P', false, 0x7F800000FF800000ul, 0x0000000000000000ul)] + [TestCase(0x2E219820u, 0x7F800000FF800000ul, 0x0000000000000000ul, 'M', false, 0x7F800000FF800000ul, 0x0000000000000000ul)] + [TestCase(0x2E219820u, 0x7F800000FF800000ul, 0x0000000000000000ul, 'Z', false, 0x7F800000FF800000ul, 0x0000000000000000ul)] + [TestCase(0x2E219820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, 'N', false, 0xFFC000017FC00002ul, 0x0000000000000000ul, Ignore = "NaN test.")] + [TestCase(0x2E219820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, 'P', false, 0xFFC000017FC00002ul, 0x0000000000000000ul, Ignore = "NaN test.")] + [TestCase(0x2E219820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, 'M', false, 0xFFC000017FC00002ul, 0x0000000000000000ul, Ignore = "NaN test.")] + [TestCase(0x2E219820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, 'Z', false, 0xFFC000017FC00002ul, 0x0000000000000000ul, Ignore = "NaN test.")] + [TestCase(0x2E219820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, 'N', true, 0x7FC000007FC00000ul, 0x0000000000000000ul, Ignore = "NaN test.")] + [TestCase(0x2E219820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, 'P', true, 0x7FC000007FC00000ul, 0x0000000000000000ul, Ignore = "NaN test.")] + [TestCase(0x2E219820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, 'M', true, 0x7FC000007FC00000ul, 0x0000000000000000ul, Ignore = "NaN test.")] + [TestCase(0x2E219820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, 'Z', true, 0x7FC000007FC00000ul, 0x0000000000000000ul, Ignore = "NaN test.")] + public void Frintx_V(uint Opcode, ulong A, ulong B, char RoundType, bool DefaultNaN, ulong Result0, ulong Result1) + { + int FpcrTemp = 0x0; + switch(RoundType) + { + case 'N': + FpcrTemp = 0x0; + break; + + case 'P': + FpcrTemp = 0x400000; + break; + + case 'M': + FpcrTemp = 0x800000; + break; + + case 'Z': + FpcrTemp = 0xC00000; + break; + } + if(DefaultNaN) + { + FpcrTemp |= 1 << 25; + } + AVec V1 = new AVec { X0 = A, X1 = B }; + AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, Fpcr: FpcrTemp); + Assert.Multiple(() => + { + Assert.AreEqual(Result0, ThreadState.V0.X0); + Assert.AreEqual(Result1, ThreadState.V0.X1); + }); + } + + [TestCase(0x41200000u, 0x3EA18000u)] + public void Frsqrte_S(uint A, uint Result) + { + AVec V1 = new AVec { X0 = A }; + AThreadState ThreadState = SingleOpcode(0x7EA1D820, V1: V1); + Assert.AreEqual(Result, ThreadState.V0.X0); + } } } diff --git a/Ryujinx.Tests/Cpu/CpuTestSimdMove.cs b/Ryujinx.Tests/Cpu/CpuTestSimdMove.cs index 372689d08..0681b6139 100644 --- a/Ryujinx.Tests/Cpu/CpuTestSimdMove.cs +++ b/Ryujinx.Tests/Cpu/CpuTestSimdMove.cs @@ -5,6 +5,82 @@ namespace Ryujinx.Tests.Cpu { public class CpuTestSimdMove : CpuTest { + [Test, Description("trn1 v0.4s, v1.4s, v2.4s")] + public void Trn1_V_4S([Random(2)] uint A0, [Random(2)] uint A1, [Random(2)] uint A2, [Random(2)] uint A3, + [Random(2)] uint B0, [Random(2)] uint B1, [Random(2)] uint B2, [Random(2)] uint B3) + { + uint Opcode = 0x4E822820; + AVec V1 = new AVec { W0 = A0, W1 = A1, W2 = A2, W3 = A3 }; + AVec V2 = new AVec { W0 = B0, W1 = B1, W2 = B2, W3 = B3 }; + + AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, V2: V2); + + Assert.That(ThreadState.V0.W0, Is.EqualTo(A0)); + Assert.That(ThreadState.V0.W1, Is.EqualTo(B0)); + Assert.That(ThreadState.V0.W2, Is.EqualTo(A2)); + Assert.That(ThreadState.V0.W3, Is.EqualTo(B2)); + } + + [Test, Description("trn1 v0.8b, v1.8b, v2.8b")] + public void Trn1_V_8B([Random(2)] byte A0, [Random(1)] byte A1, [Random(2)] byte A2, [Random(1)] byte A3, + [Random(2)] byte A4, [Random(1)] byte A5, [Random(2)] byte A6, [Random(1)] byte A7, + [Random(2)] byte B0, [Random(1)] byte B1, [Random(2)] byte B2, [Random(1)] byte B3, + [Random(2)] byte B4, [Random(1)] byte B5, [Random(2)] byte B6, [Random(1)] byte B7) + { + uint Opcode = 0x0E022820; + AVec V1 = new AVec { B0 = A0, B1 = A1, B2 = A2, B3 = A3, B4 = A4, B5 = A5, B6 = A6, B7 = A7 }; + AVec V2 = new AVec { B0 = B0, B1 = B1, B2 = B2, B3 = B3, B4 = B4, B5 = B5, B6 = B6, B7 = B7 }; + + AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, V2: V2); + + Assert.That(ThreadState.V0.B0, Is.EqualTo(A0)); + Assert.That(ThreadState.V0.B1, Is.EqualTo(B0)); + Assert.That(ThreadState.V0.B2, Is.EqualTo(A2)); + Assert.That(ThreadState.V0.B3, Is.EqualTo(B2)); + Assert.That(ThreadState.V0.B4, Is.EqualTo(A4)); + Assert.That(ThreadState.V0.B5, Is.EqualTo(B4)); + Assert.That(ThreadState.V0.B6, Is.EqualTo(A6)); + Assert.That(ThreadState.V0.B7, Is.EqualTo(B6)); + } + + [Test, Description("trn2 v0.4s, v1.4s, v2.4s")] + public void Trn2_V_4S([Random(2)] uint A0, [Random(2)] uint A1, [Random(2)] uint A2, [Random(2)] uint A3, + [Random(2)] uint B0, [Random(2)] uint B1, [Random(2)] uint B2, [Random(2)] uint B3) + { + uint Opcode = 0x4E826820; + AVec V1 = new AVec { W0 = A0, W1 = A1, W2 = A2, W3 = A3 }; + AVec V2 = new AVec { W0 = B0, W1 = B1, W2 = B2, W3 = B3 }; + + AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, V2: V2); + + Assert.That(ThreadState.V0.W0, Is.EqualTo(A1)); + Assert.That(ThreadState.V0.W1, Is.EqualTo(B1)); + Assert.That(ThreadState.V0.W2, Is.EqualTo(A3)); + Assert.That(ThreadState.V0.W3, Is.EqualTo(B3)); + } + + [Test, Description("trn2 v0.8b, v1.8b, v2.8b")] + public void Trn2_V_8B([Random(1)] byte A0, [Random(2)] byte A1, [Random(1)] byte A2, [Random(2)] byte A3, + [Random(1)] byte A4, [Random(2)] byte A5, [Random(1)] byte A6, [Random(2)] byte A7, + [Random(1)] byte B0, [Random(2)] byte B1, [Random(1)] byte B2, [Random(2)] byte B3, + [Random(1)] byte B4, [Random(2)] byte B5, [Random(1)] byte B6, [Random(2)] byte B7) + { + uint Opcode = 0x0E026820; + AVec V1 = new AVec { B0 = A0, B1 = A1, B2 = A2, B3 = A3, B4 = A4, B5 = A5, B6 = A6, B7 = A7 }; + AVec V2 = new AVec { B0 = B0, B1 = B1, B2 = B2, B3 = B3, B4 = B4, B5 = B5, B6 = B6, B7 = B7 }; + + AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, V2: V2); + + Assert.That(ThreadState.V0.B0, Is.EqualTo(A1)); + Assert.That(ThreadState.V0.B1, Is.EqualTo(B1)); + Assert.That(ThreadState.V0.B2, Is.EqualTo(A3)); + Assert.That(ThreadState.V0.B3, Is.EqualTo(B3)); + Assert.That(ThreadState.V0.B4, Is.EqualTo(A5)); + Assert.That(ThreadState.V0.B5, Is.EqualTo(B5)); + Assert.That(ThreadState.V0.B6, Is.EqualTo(A7)); + Assert.That(ThreadState.V0.B7, Is.EqualTo(B7)); + } + [TestCase(0u, 0u, 0x2313221221112010ul, 0x0000000000000000ul)] [TestCase(1u, 0u, 0x2313221221112010ul, 0x2717261625152414ul)] [TestCase(0u, 1u, 0x2322131221201110ul, 0x0000000000000000ul)] diff --git a/Ryujinx.Tests/Cpu/CpuTestSimdReg.cs b/Ryujinx.Tests/Cpu/CpuTestSimdReg.cs new file mode 100644 index 000000000..8c13c33a2 --- /dev/null +++ b/Ryujinx.Tests/Cpu/CpuTestSimdReg.cs @@ -0,0 +1,482 @@ +#define SimdReg + +using ChocolArm64.State; + +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + using Tester; + using Tester.Types; + + [Category("SimdReg")] + public sealed class CpuTestSimdReg : CpuTest + { +#if SimdReg + [SetUp] + public void SetupTester() + { + AArch64.TakeReset(false); + } + +#region "ValueSource" + private static ulong[] _1D_() + { + return new ulong[] { 0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul }; + } + + private static ulong[] _8B4H2S_() + { + return new ulong[] { 0x0000000000000000ul, 0x7F7F7F7F7F7F7F7Ful, + 0x8080808080808080ul, 0x7FFF7FFF7FFF7FFFul, + 0x8000800080008000ul, 0x7FFFFFFF7FFFFFFFul, + 0x8000000080000000ul, 0xFFFFFFFFFFFFFFFFul }; + } + + private static ulong[] _8B4H2S1D_() + { + return new ulong[] { 0x0000000000000000ul, 0x7F7F7F7F7F7F7F7Ful, + 0x8080808080808080ul, 0x7FFF7FFF7FFF7FFFul, + 0x8000800080008000ul, 0x7FFFFFFF7FFFFFFFul, + 0x8000000080000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul }; + } + + private static ulong[] _4H2S1D_() + { + return new ulong[] { 0x0000000000000000ul, 0x7FFF7FFF7FFF7FFFul, + 0x8000800080008000ul, 0x7FFFFFFF7FFFFFFFul, + 0x8000000080000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul }; + } +#endregion + + [Test, Description("ADD , , ")] + public void Add_S_D([ValueSource("_1D_")] [Random(1)] ulong A, + [ValueSource("_1D_")] [Random(1)] ulong B) + { + uint Opcode = 0x5EE28420; // ADD D0, D1, D2 + Bits Op = new Bits(Opcode); + + AVec V0 = new AVec { X1 = TestContext.CurrentContext.Random.NextULong() }; + AVec V1 = new AVec { X0 = A }; + AVec V2 = new AVec { X0 = B }; + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + + AArch64.V(1, new Bits(A)); + AArch64.V(2, new Bits(B)); + SimdFp.Add_S(Op[23, 22], Op[20, 16], 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); + } + + [Test, Description("ADD ., ., .")] + public void Add_V_8B_4H_2S([ValueSource("_8B4H2S_")] [Random(1)] ulong A, + [ValueSource("_8B4H2S_")] [Random(1)] ulong B, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint Opcode = 0x0E228420; // ADD V0.8B, V1.8B, V2.8B + Opcode |= ((size & 3) << 22); + Bits Op = new Bits(Opcode); + + AVec V0 = new AVec { X1 = TestContext.CurrentContext.Random.NextULong() }; + AVec V1 = new AVec { X0 = A }; + AVec V2 = new AVec { X0 = B }; + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + + AArch64.V(1, new Bits(A)); + AArch64.V(2, new Bits(B)); + SimdFp.Add_V(Op[30], Op[23, 22], Op[20, 16], 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); + } + + [Test, Pairwise, Description("ADD ., ., .")] + public void Add_V_16B_8H_4S_2D([ValueSource("_8B4H2S1D_")] [Random(1)] ulong A0, + [ValueSource("_8B4H2S1D_")] [Random(1)] ulong A1, + [ValueSource("_8B4H2S1D_")] [Random(1)] ulong B0, + [ValueSource("_8B4H2S1D_")] [Random(1)] ulong B1, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint Opcode = 0x4E228420; // ADD V0.16B, V1.16B, V2.16B + Opcode |= ((size & 3) << 22); + Bits Op = new Bits(Opcode); + + AVec V1 = new AVec { X0 = A0, X1 = A1 }; + AVec V2 = new AVec { X0 = B0, X1 = B1 }; + AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, V2: V2); + + AArch64.Vpart(1, 0, new Bits(A0)); + AArch64.Vpart(1, 1, new Bits(A1)); + AArch64.Vpart(2, 0, new Bits(B0)); + AArch64.Vpart(2, 1, new Bits(B1)); + SimdFp.Add_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + + Assert.Multiple(() => + { + Assert.That(ThreadState.V0.X0, Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); + Assert.That(ThreadState.V0.X1, Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); + }); + } + + [Test, Pairwise, Description("ADDHN{2} ., ., .")] + public void Addhn_V_8H8B_4S4H_2D2S([ValueSource("_4H2S1D_")] [Random(1)] ulong A0, + [ValueSource("_4H2S1D_")] [Random(1)] ulong A1, + [ValueSource("_4H2S1D_")] [Random(1)] ulong B0, + [ValueSource("_4H2S1D_")] [Random(1)] ulong B1, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8H8B, 4S4H, 2D2S> + { + uint Opcode = 0x0E224020; // ADDHN V0.8B, V1.8H, V2.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 }; + AVec V2 = new AVec { X0 = B0, X1 = B1 }; + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + + AArch64.Vpart(1, 0, new Bits(A0)); + AArch64.Vpart(1, 1, new Bits(A1)); + AArch64.Vpart(2, 0, new Bits(B0)); + AArch64.Vpart(2, 1, new Bits(B1)); + SimdFp.Addhn_V(Op[30], Op[23, 22], Op[20, 16], 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); + }); + } + + [Test, Pairwise, Description("ADDHN{2} ., ., .")] + public void Addhn_V_8H16B_4S8H_2D4S([ValueSource("_4H2S1D_")] [Random(1)] ulong A0, + [ValueSource("_4H2S1D_")] [Random(1)] ulong A1, + [ValueSource("_4H2S1D_")] [Random(1)] ulong B0, + [ValueSource("_4H2S1D_")] [Random(1)] ulong B1, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8H16B, 4S8H, 2D4S> + { + uint Opcode = 0x4E224020; // ADDHN2 V0.16B, V1.8H, V2.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 }; + AVec V2 = new AVec { X0 = B0, X1 = B1 }; + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + + AArch64.Vpart(1, 0, new Bits(A0)); + AArch64.Vpart(1, 1, new Bits(A1)); + AArch64.Vpart(2, 0, new Bits(B0)); + AArch64.Vpart(2, 1, new Bits(B1)); + SimdFp.Addhn_V(Op[30], Op[23, 22], Op[20, 16], 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())); + }); + } + + [Test, Description("ADDP ., ., .")] + public void Addp_V_8B_4H_2S([ValueSource("_8B4H2S_")] [Random(1)] ulong A, + [ValueSource("_8B4H2S_")] [Random(1)] ulong B, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint Opcode = 0x0E22BC20; // ADDP V0.8B, V1.8B, V2.8B + Opcode |= ((size & 3) << 22); + Bits Op = new Bits(Opcode); + + AVec V0 = new AVec { X1 = TestContext.CurrentContext.Random.NextULong() }; + AVec V1 = new AVec { X0 = A }; + AVec V2 = new AVec { X0 = B }; + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + + AArch64.V(1, new Bits(A)); + AArch64.V(2, new Bits(B)); + SimdFp.Addp_V(Op[30], Op[23, 22], Op[20, 16], 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); + } + + [Test, Pairwise, Description("ADDP ., ., .")] + public void Addp_V_16B_8H_4S_2D([ValueSource("_8B4H2S1D_")] [Random(1)] ulong A0, + [ValueSource("_8B4H2S1D_")] [Random(1)] ulong A1, + [ValueSource("_8B4H2S1D_")] [Random(1)] ulong B0, + [ValueSource("_8B4H2S1D_")] [Random(1)] ulong B1, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint Opcode = 0x4E22BC20; // ADDP V0.16B, V1.16B, V2.16B + Opcode |= ((size & 3) << 22); + Bits Op = new Bits(Opcode); + + AVec V1 = new AVec { X0 = A0, X1 = A1 }; + AVec V2 = new AVec { X0 = B0, X1 = B1 }; + AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, V2: V2); + + AArch64.Vpart(1, 0, new Bits(A0)); + AArch64.Vpart(1, 1, new Bits(A1)); + AArch64.Vpart(2, 0, new Bits(B0)); + AArch64.Vpart(2, 1, new Bits(B1)); + SimdFp.Addp_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + + Assert.Multiple(() => + { + Assert.That(ThreadState.V0.X0, Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); + Assert.That(ThreadState.V0.X1, Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); + }); + } + + [Test, Pairwise, Description("RADDHN{2} ., ., .")] + public void Raddhn_V_8H8B_4S4H_2D2S([ValueSource("_4H2S1D_")] [Random(1)] ulong A0, + [ValueSource("_4H2S1D_")] [Random(1)] ulong A1, + [ValueSource("_4H2S1D_")] [Random(1)] ulong B0, + [ValueSource("_4H2S1D_")] [Random(1)] ulong B1, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8H8B, 4S4H, 2D2S> + { + uint Opcode = 0x2E224020; // RADDHN V0.8B, V1.8H, V2.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 }; + AVec V2 = new AVec { X0 = B0, X1 = B1 }; + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + + AArch64.Vpart(1, 0, new Bits(A0)); + AArch64.Vpart(1, 1, new Bits(A1)); + AArch64.Vpart(2, 0, new Bits(B0)); + AArch64.Vpart(2, 1, new Bits(B1)); + SimdFp.Raddhn_V(Op[30], Op[23, 22], Op[20, 16], 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); + }); + } + + [Test, Pairwise, Description("RADDHN{2} ., ., .")] + public void Raddhn_V_8H16B_4S8H_2D4S([ValueSource("_4H2S1D_")] [Random(1)] ulong A0, + [ValueSource("_4H2S1D_")] [Random(1)] ulong A1, + [ValueSource("_4H2S1D_")] [Random(1)] ulong B0, + [ValueSource("_4H2S1D_")] [Random(1)] ulong B1, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8H16B, 4S8H, 2D4S> + { + uint Opcode = 0x6E224020; // RADDHN2 V0.16B, V1.8H, V2.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 }; + AVec V2 = new AVec { X0 = B0, X1 = B1 }; + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + + AArch64.Vpart(1, 0, new Bits(A0)); + AArch64.Vpart(1, 1, new Bits(A1)); + AArch64.Vpart(2, 0, new Bits(B0)); + AArch64.Vpart(2, 1, new Bits(B1)); + SimdFp.Raddhn_V(Op[30], Op[23, 22], Op[20, 16], 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())); + }); + } + + [Test, Pairwise, Description("RSUBHN{2} ., ., .")] + public void Rsubhn_V_8H8B_4S4H_2D2S([ValueSource("_4H2S1D_")] [Random(1)] ulong A0, + [ValueSource("_4H2S1D_")] [Random(1)] ulong A1, + [ValueSource("_4H2S1D_")] [Random(1)] ulong B0, + [ValueSource("_4H2S1D_")] [Random(1)] ulong B1, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8H8B, 4S4H, 2D2S> + { + uint Opcode = 0x2E226020; // RSUBHN V0.8B, V1.8H, V2.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 }; + AVec V2 = new AVec { X0 = B0, X1 = B1 }; + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + + AArch64.Vpart(1, 0, new Bits(A0)); + AArch64.Vpart(1, 1, new Bits(A1)); + AArch64.Vpart(2, 0, new Bits(B0)); + AArch64.Vpart(2, 1, new Bits(B1)); + SimdFp.Rsubhn_V(Op[30], Op[23, 22], Op[20, 16], 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); + }); + } + + [Test, Pairwise, Description("RSUBHN{2} ., ., .")] + public void Rsubhn_V_8H16B_4S8H_2D4S([ValueSource("_4H2S1D_")] [Random(1)] ulong A0, + [ValueSource("_4H2S1D_")] [Random(1)] ulong A1, + [ValueSource("_4H2S1D_")] [Random(1)] ulong B0, + [ValueSource("_4H2S1D_")] [Random(1)] ulong B1, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8H16B, 4S8H, 2D4S> + { + uint Opcode = 0x6E226020; // RSUBHN2 V0.16B, V1.8H, V2.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 }; + AVec V2 = new AVec { X0 = B0, X1 = B1 }; + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + + AArch64.Vpart(1, 0, new Bits(A0)); + AArch64.Vpart(1, 1, new Bits(A1)); + AArch64.Vpart(2, 0, new Bits(B0)); + AArch64.Vpart(2, 1, new Bits(B1)); + SimdFp.Rsubhn_V(Op[30], Op[23, 22], Op[20, 16], 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())); + }); + } + + [Test, Description("SUB , , ")] + public void Sub_S_D([ValueSource("_1D_")] [Random(1)] ulong A, + [ValueSource("_1D_")] [Random(1)] ulong B) + { + uint Opcode = 0x7EE28420; // SUB D0, D1, D2 + Bits Op = new Bits(Opcode); + + AVec V0 = new AVec { X1 = TestContext.CurrentContext.Random.NextULong() }; + AVec V1 = new AVec { X0 = A }; + AVec V2 = new AVec { X0 = B }; + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + + AArch64.V(1, new Bits(A)); + AArch64.V(2, new Bits(B)); + SimdFp.Sub_S(Op[23, 22], Op[20, 16], 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); + } + + [Test, Description("SUB ., ., .")] + public void Sub_V_8B_4H_2S([ValueSource("_8B4H2S_")] [Random(1)] ulong A, + [ValueSource("_8B4H2S_")] [Random(1)] ulong B, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint Opcode = 0x2E228420; // SUB V0.8B, V1.8B, V2.8B + Opcode |= ((size & 3) << 22); + Bits Op = new Bits(Opcode); + + AVec V0 = new AVec { X1 = TestContext.CurrentContext.Random.NextULong() }; + AVec V1 = new AVec { X0 = A }; + AVec V2 = new AVec { X0 = B }; + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + + AArch64.V(1, new Bits(A)); + AArch64.V(2, new Bits(B)); + SimdFp.Sub_V(Op[30], Op[23, 22], Op[20, 16], 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); + } + + [Test, Pairwise, Description("SUB ., ., .")] + public void Sub_V_16B_8H_4S_2D([ValueSource("_8B4H2S1D_")] [Random(1)] ulong A0, + [ValueSource("_8B4H2S1D_")] [Random(1)] ulong A1, + [ValueSource("_8B4H2S1D_")] [Random(1)] ulong B0, + [ValueSource("_8B4H2S1D_")] [Random(1)] ulong B1, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint Opcode = 0x6E228420; // SUB V0.16B, V1.16B, V2.16B + Opcode |= ((size & 3) << 22); + Bits Op = new Bits(Opcode); + + AVec V1 = new AVec { X0 = A0, X1 = A1 }; + AVec V2 = new AVec { X0 = B0, X1 = B1 }; + AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, V2: V2); + + AArch64.Vpart(1, 0, new Bits(A0)); + AArch64.Vpart(1, 1, new Bits(A1)); + AArch64.Vpart(2, 0, new Bits(B0)); + AArch64.Vpart(2, 1, new Bits(B1)); + SimdFp.Sub_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + + Assert.Multiple(() => + { + Assert.That(ThreadState.V0.X0, Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); + Assert.That(ThreadState.V0.X1, Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); + }); + } + + [Test, Pairwise, Description("SUBHN{2} ., ., .")] + public void Subhn_V_8H8B_4S4H_2D2S([ValueSource("_4H2S1D_")] [Random(1)] ulong A0, + [ValueSource("_4H2S1D_")] [Random(1)] ulong A1, + [ValueSource("_4H2S1D_")] [Random(1)] ulong B0, + [ValueSource("_4H2S1D_")] [Random(1)] ulong B1, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8H8B, 4S4H, 2D2S> + { + uint Opcode = 0x0E226020; // SUBHN V0.8B, V1.8H, V2.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 }; + AVec V2 = new AVec { X0 = B0, X1 = B1 }; + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + + AArch64.Vpart(1, 0, new Bits(A0)); + AArch64.Vpart(1, 1, new Bits(A1)); + AArch64.Vpart(2, 0, new Bits(B0)); + AArch64.Vpart(2, 1, new Bits(B1)); + SimdFp.Subhn_V(Op[30], Op[23, 22], Op[20, 16], 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); + }); + } + + [Test, Pairwise, Description("SUBHN{2} ., ., .")] + public void Subhn_V_8H16B_4S8H_2D4S([ValueSource("_4H2S1D_")] [Random(1)] ulong A0, + [ValueSource("_4H2S1D_")] [Random(1)] ulong A1, + [ValueSource("_4H2S1D_")] [Random(1)] ulong B0, + [ValueSource("_4H2S1D_")] [Random(1)] ulong B1, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8H16B, 4S8H, 2D4S> + { + uint Opcode = 0x4E226020; // SUBHN2 V0.16B, V1.8H, V2.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 }; + AVec V2 = new AVec { X0 = B0, X1 = B1 }; + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + + AArch64.Vpart(1, 0, new Bits(A0)); + AArch64.Vpart(1, 1, new Bits(A1)); + AArch64.Vpart(2, 0, new Bits(B0)); + AArch64.Vpart(2, 1, new Bits(B1)); + SimdFp.Subhn_V(Op[30], Op[23, 22], Op[20, 16], 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())); + }); + } +#endif + } +} diff --git a/Ryujinx.Tests/Cpu/Tester/Instructions.cs b/Ryujinx.Tests/Cpu/Tester/Instructions.cs new file mode 100644 index 000000000..e866a9a0d --- /dev/null +++ b/Ryujinx.Tests/Cpu/Tester/Instructions.cs @@ -0,0 +1,2333 @@ +// https://github.com/LDj3SNuD/ARM_v8-A_AArch64_Instructions_Tester/blob/master/Tester/Instructions.cs + +// https://meriac.github.io/archex/A64_v83A_ISA/index.xml +// https://meriac.github.io/archex/A64_v83A_ISA/fpsimdindex.xml + +using System.Numerics; + +namespace Ryujinx.Tests.Cpu.Tester +{ + using Types; + + using static AArch64; + using static Shared; + + internal static class Base + { +#region "Alu" + // https://meriac.github.io/archex/A64_v83A_ISA/cls_int.xml + public static void Cls(bool sf, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int datasize = (sf ? 64 : 32); + + /* Operation */ + Bits operand1 = X(datasize, n); + + BigInteger result = (BigInteger)CountLeadingSignBits(operand1); + + X(d, result.SubBigInteger(datasize - 1, 0)); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/clz_int.xml + public static void Clz(bool sf, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int datasize = (sf ? 64 : 32); + + /* Operation */ + Bits operand1 = X(datasize, n); + + BigInteger result = (BigInteger)CountLeadingZeroBits(operand1); + + X(d, result.SubBigInteger(datasize - 1, 0)); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/rbit_int.xml + public static void Rbit(bool sf, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int datasize = (sf ? 64 : 32); + + /* Operation */ + Bits result = new Bits(datasize); + Bits operand = X(datasize, n); + + for (int i = 0; i <= datasize - 1; i++) + { + result[datasize - 1 - i] = operand[i]; + } + + X(d, result); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/rev16_int.xml + public static void Rev16(bool sf, Bits Rn, Bits Rd) + { + /* Bits opc = "01"; */ + + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int datasize = (sf ? 64 : 32); + + int container_size = 16; + + /* Operation */ + Bits result = new Bits(datasize); + Bits operand = X(datasize, n); + + int containers = datasize / container_size; + int elements_per_container = container_size / 8; + int index = 0; + int rev_index; + + for (int c = 0; c <= containers - 1; c++) + { + rev_index = index + ((elements_per_container - 1) * 8); + + for (int e = 0; e <= elements_per_container - 1; e++) + { + result[rev_index + 7, rev_index] = operand[index + 7, index]; + + index = index + 8; + rev_index = rev_index - 8; + } + } + + X(d, result); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/rev32_int.xml + // (https://meriac.github.io/archex/A64_v83A_ISA/rev.xml) + public static void Rev32(bool sf, Bits Rn, Bits Rd) + { + /* Bits opc = "10"; */ + + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int datasize = (sf ? 64 : 32); + + int container_size = 32; + + /* Operation */ + Bits result = new Bits(datasize); + Bits operand = X(datasize, n); + + int containers = datasize / container_size; + int elements_per_container = container_size / 8; + int index = 0; + int rev_index; + + for (int c = 0; c <= containers - 1; c++) + { + rev_index = index + ((elements_per_container - 1) * 8); + + for (int e = 0; e <= elements_per_container - 1; e++) + { + result[rev_index + 7, rev_index] = operand[index + 7, index]; + + index = index + 8; + rev_index = rev_index - 8; + } + } + + X(d, result); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/rev64_rev.xml + // (https://meriac.github.io/archex/A64_v83A_ISA/rev.xml) + public static void Rev64(Bits Rn, Bits Rd) + { + /* Bits opc = "11"; */ + + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + + int container_size = 64; + + /* Operation */ + Bits result = new Bits(64); + Bits operand = X(64, n); + + int containers = 64 / container_size; + int elements_per_container = container_size / 8; + int index = 0; + int rev_index; + + for (int c = 0; c <= containers - 1; c++) + { + rev_index = index + ((elements_per_container - 1) * 8); + + for (int e = 0; e <= elements_per_container - 1; e++) + { + result[rev_index + 7, rev_index] = operand[index + 7, index]; + + index = index + 8; + rev_index = rev_index - 8; + } + } + + X(d, result); + } +#endregion + +#region "AluImm" + // https://meriac.github.io/archex/A64_v83A_ISA/add_addsub_imm.xml + public static void Add_Imm(bool sf, Bits shift, Bits imm12, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int datasize = (sf ? 64 : 32); + + Bits imm; + + switch (shift) + { + default: + case Bits bits when bits == "00": + imm = ZeroExtend(imm12, datasize); + break; + case Bits bits when bits == "01": + imm = ZeroExtend(Bits.Concat(imm12, Zeros(12)), datasize); + break; + /* when '1x' ReservedValue(); */ + } + + /* Operation */ + Bits result; + Bits operand1 = (n == 31 ? SP(datasize) : X(datasize, n)); + + (result, _) = AddWithCarry(datasize, operand1, imm, false); + + if (d == 31) + { + SP(result); + } + else + { + X(d, result); + } + } + + // https://meriac.github.io/archex/A64_v83A_ISA/adds_addsub_imm.xml + public static void Adds_Imm(bool sf, Bits shift, Bits imm12, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int datasize = (sf ? 64 : 32); + + Bits imm; + + switch (shift) + { + default: + case Bits bits when bits == "00": + imm = ZeroExtend(imm12, datasize); + break; + case Bits bits when bits == "01": + imm = ZeroExtend(Bits.Concat(imm12, Zeros(12)), datasize); + break; + /* when '1x' ReservedValue(); */ + } + + /* Operation */ + Bits result; + Bits operand1 = (n == 31 ? SP(datasize) : X(datasize, n)); + Bits nzcv; + + (result, nzcv) = AddWithCarry(datasize, operand1, imm, false); + + PSTATE.NZCV(nzcv); + + X(d, result); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/and_log_imm.xml + public static void And_Imm(bool sf, bool N, Bits immr, Bits imms, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int datasize = (sf ? 64 : 32); + + Bits imm; + + /* if sf == '0' && N != '0' then ReservedValue(); */ + + (imm, _) = DecodeBitMasks(datasize, N, imms, immr, true); + + /* Operation */ + Bits operand1 = X(datasize, n); + + Bits result = AND(operand1, imm); + + if (d == 31) + { + SP(result); + } + else + { + X(d, result); + } + } + + // https://meriac.github.io/archex/A64_v83A_ISA/ands_log_imm.xml + public static void Ands_Imm(bool sf, bool N, Bits immr, Bits imms, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int datasize = (sf ? 64 : 32); + + Bits imm; + + /* if sf == '0' && N != '0' then ReservedValue(); */ + + (imm, _) = DecodeBitMasks(datasize, N, imms, immr, true); + + /* Operation */ + Bits operand1 = X(datasize, n); + + Bits result = AND(operand1, imm); + + PSTATE.NZCV(result[datasize - 1], IsZeroBit(result), false, false); + + X(d, result); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/eor_log_imm.xml + public static void Eor_Imm(bool sf, bool N, Bits immr, Bits imms, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int datasize = (sf ? 64 : 32); + + Bits imm; + + /* if sf == '0' && N != '0' then ReservedValue(); */ + + (imm, _) = DecodeBitMasks(datasize, N, imms, immr, true); + + /* Operation */ + Bits operand1 = X(datasize, n); + + Bits result = EOR(operand1, imm); + + if (d == 31) + { + SP(result); + } + else + { + X(d, result); + } + } + + // https://meriac.github.io/archex/A64_v83A_ISA/orr_log_imm.xml + public static void Orr_Imm(bool sf, bool N, Bits immr, Bits imms, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int datasize = (sf ? 64 : 32); + + Bits imm; + + /* if sf == '0' && N != '0' then ReservedValue(); */ + + (imm, _) = DecodeBitMasks(datasize, N, imms, immr, true); + + /* Operation */ + Bits operand1 = X(datasize, n); + + Bits result = OR(operand1, imm); + + if (d == 31) + { + SP(result); + } + else + { + X(d, result); + } + } + + // https://meriac.github.io/archex/A64_v83A_ISA/sub_addsub_imm.xml + public static void Sub_Imm(bool sf, Bits shift, Bits imm12, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int datasize = (sf ? 64 : 32); + + Bits imm; + + switch (shift) + { + default: + case Bits bits when bits == "00": + imm = ZeroExtend(imm12, datasize); + break; + case Bits bits when bits == "01": + imm = ZeroExtend(Bits.Concat(imm12, Zeros(12)), datasize); + break; + /* when '1x' ReservedValue(); */ + } + + /* Operation */ + Bits result; + Bits operand1 = (n == 31 ? SP(datasize) : X(datasize, n)); + Bits operand2 = NOT(imm); + + (result, _) = AddWithCarry(datasize, operand1, operand2, true); + + if (d == 31) + { + SP(result); + } + else + { + X(d, result); + } + } + + // https://meriac.github.io/archex/A64_v83A_ISA/subs_addsub_imm.xml + public static void Subs_Imm(bool sf, Bits shift, Bits imm12, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int datasize = (sf ? 64 : 32); + + Bits imm; + + switch (shift) + { + default: + case Bits bits when bits == "00": + imm = ZeroExtend(imm12, datasize); + break; + case Bits bits when bits == "01": + imm = ZeroExtend(Bits.Concat(imm12, Zeros(12)), datasize); + break; + /* when '1x' ReservedValue(); */ + } + + /* Operation */ + Bits result; + Bits operand1 = (n == 31 ? SP(datasize) : X(datasize, n)); + Bits operand2 = NOT(imm); + Bits nzcv; + + (result, nzcv) = AddWithCarry(datasize, operand1, operand2, true); + + PSTATE.NZCV(nzcv); + + X(d, result); + } +#endregion + +#region "AluRs" + // https://meriac.github.io/archex/A64_v83A_ISA/adc.xml + public static void Adc(bool sf, Bits Rm, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + int datasize = (sf ? 64 : 32); + + /* Operation */ + Bits result; + Bits operand1 = X(datasize, n); + Bits operand2 = X(datasize, m); + + (result, _) = AddWithCarry(datasize, operand1, operand2, PSTATE.C); + + X(d, result); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/adcs.xml + public static void Adcs(bool sf, Bits Rm, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + int datasize = (sf ? 64 : 32); + + /* Operation */ + Bits result; + Bits operand1 = X(datasize, n); + Bits operand2 = X(datasize, m); + Bits nzcv; + + (result, nzcv) = AddWithCarry(datasize, operand1, operand2, PSTATE.C); + + PSTATE.NZCV(nzcv); + + X(d, result); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/add_addsub_shift.xml + public static void Add_Rs(bool sf, Bits shift, Bits Rm, Bits imm6, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + int datasize = (sf ? 64 : 32); + + /* if shift == '11' then ReservedValue(); */ + /* if sf == '0' && imm6<5> == '1' then ReservedValue(); */ + + ShiftType shift_type = DecodeShift(shift); + int shift_amount = (int)UInt(imm6); + + /* Operation */ + Bits result; + Bits operand1 = X(datasize, n); + Bits operand2 = ShiftReg(datasize, m, shift_type, shift_amount); + + (result, _) = AddWithCarry(datasize, operand1, operand2, false); + + X(d, result); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/adds_addsub_shift.xml + public static void Adds_Rs(bool sf, Bits shift, Bits Rm, Bits imm6, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + int datasize = (sf ? 64 : 32); + + /* if shift == '11' then ReservedValue(); */ + /* if sf == '0' && imm6<5> == '1' then ReservedValue(); */ + + ShiftType shift_type = DecodeShift(shift); + int shift_amount = (int)UInt(imm6); + + /* Operation */ + Bits result; + Bits operand1 = X(datasize, n); + Bits operand2 = ShiftReg(datasize, m, shift_type, shift_amount); + Bits nzcv; + + (result, nzcv) = AddWithCarry(datasize, operand1, operand2, false); + + PSTATE.NZCV(nzcv); + + X(d, result); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/and_log_shift.xml + public static void And_Rs(bool sf, Bits shift, Bits Rm, Bits imm6, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + int datasize = (sf ? 64 : 32); + + /* if sf == '0' && imm6<5> == '1' then ReservedValue(); */ + + ShiftType shift_type = DecodeShift(shift); + int shift_amount = (int)UInt(imm6); + + /* Operation */ + Bits operand1 = X(datasize, n); + Bits operand2 = ShiftReg(datasize, m, shift_type, shift_amount); + + Bits result = AND(operand1, operand2); + + X(d, result); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/ands_log_shift.xml + public static void Ands_Rs(bool sf, Bits shift, Bits Rm, Bits imm6, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + int datasize = (sf ? 64 : 32); + + /* if sf == '0' && imm6<5> == '1' then ReservedValue(); */ + + ShiftType shift_type = DecodeShift(shift); + int shift_amount = (int)UInt(imm6); + + /* Operation */ + Bits operand1 = X(datasize, n); + Bits operand2 = ShiftReg(datasize, m, shift_type, shift_amount); + + Bits result = AND(operand1, operand2); + + PSTATE.NZCV(result[datasize - 1], IsZeroBit(result), false, false); + + X(d, result); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/asrv.xml + public static void Asrv(bool sf, Bits Rm, Bits Rn, Bits Rd) + { + Bits op2 = "10"; + + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + int datasize = (sf ? 64 : 32); + + ShiftType shift_type = DecodeShift(op2); + + /* Operation */ + Bits operand2 = X(datasize, m); + + Bits result = ShiftReg(datasize, n, shift_type, (int)(UInt(operand2) % datasize)); // BigInteger.Modulus Operator (BigInteger, BigInteger) + + X(d, result); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/bic_log_shift.xml + public static void Bic(bool sf, Bits shift, Bits Rm, Bits imm6, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + int datasize = (sf ? 64 : 32); + + /* if sf == '0' && imm6<5> == '1' then ReservedValue(); */ + + ShiftType shift_type = DecodeShift(shift); + int shift_amount = (int)UInt(imm6); + + /* Operation */ + Bits operand1 = X(datasize, n); + Bits operand2 = ShiftReg(datasize, m, shift_type, shift_amount); + + operand2 = NOT(operand2); + + Bits result = AND(operand1, operand2); + + X(d, result); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/bics.xml + public static void Bics(bool sf, Bits shift, Bits Rm, Bits imm6, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + int datasize = (sf ? 64 : 32); + + /* if sf == '0' && imm6<5> == '1' then ReservedValue(); */ + + ShiftType shift_type = DecodeShift(shift); + int shift_amount = (int)UInt(imm6); + + /* Operation */ + Bits operand1 = X(datasize, n); + Bits operand2 = ShiftReg(datasize, m, shift_type, shift_amount); + + operand2 = NOT(operand2); + + Bits result = AND(operand1, operand2); + + PSTATE.NZCV(result[datasize - 1], IsZeroBit(result), false, false); + + X(d, result); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/crc32.xml + public static void Crc32(bool sf, Bits Rm, Bits sz, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + + /* if sf == '1' && sz != '11' then UnallocatedEncoding(); */ + /* if sf == '0' && sz == '11' then UnallocatedEncoding(); */ + + int size = 8 << (int)UInt(sz); + + /* Operation */ + /* if !HaveCRCExt() then UnallocatedEncoding(); */ + + Bits acc = X(32, n); // accumulator + Bits val = X(size, m); // input value + Bits poly = new Bits(0x04C11DB7u); + + Bits tempacc = Bits.Concat(BitReverse(acc), Zeros(size)); + Bits tempval = Bits.Concat(BitReverse(val), Zeros(32)); + + // Poly32Mod2 on a bitstring does a polynomial Modulus over {0,1} operation + X(d, BitReverse(Poly32Mod2(EOR(tempacc, tempval), poly))); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/crc32c.xml + public static void Crc32c(bool sf, Bits Rm, Bits sz, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + + /* if sf == '1' && sz != '11' then UnallocatedEncoding(); */ + /* if sf == '0' && sz == '11' then UnallocatedEncoding(); */ + + int size = 8 << (int)UInt(sz); + + /* Operation */ + /* if !HaveCRCExt() then UnallocatedEncoding(); */ + + Bits acc = X(32, n); // accumulator + Bits val = X(size, m); // input value + Bits poly = new Bits(0x1EDC6F41u); + + Bits tempacc = Bits.Concat(BitReverse(acc), Zeros(size)); + Bits tempval = Bits.Concat(BitReverse(val), Zeros(32)); + + // Poly32Mod2 on a bitstring does a polynomial Modulus over {0,1} operation + X(d, BitReverse(Poly32Mod2(EOR(tempacc, tempval), poly))); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/eon.xml + public static void Eon(bool sf, Bits shift, Bits Rm, Bits imm6, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + int datasize = (sf ? 64 : 32); + + /* if sf == '0' && imm6<5> == '1' then ReservedValue(); */ + + ShiftType shift_type = DecodeShift(shift); + int shift_amount = (int)UInt(imm6); + + /* Operation */ + Bits operand1 = X(datasize, n); + Bits operand2 = ShiftReg(datasize, m, shift_type, shift_amount); + + operand2 = NOT(operand2); + + Bits result = EOR(operand1, operand2); + + X(d, result); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/eor_log_shift.xml + public static void Eor_Rs(bool sf, Bits shift, Bits Rm, Bits imm6, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + int datasize = (sf ? 64 : 32); + + /* if sf == '0' && imm6<5> == '1' then ReservedValue(); */ + + ShiftType shift_type = DecodeShift(shift); + int shift_amount = (int)UInt(imm6); + + /* Operation */ + Bits operand1 = X(datasize, n); + Bits operand2 = ShiftReg(datasize, m, shift_type, shift_amount); + + Bits result = EOR(operand1, operand2); + + X(d, result); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/extr.xml + public static void Extr(bool sf, bool N, Bits Rm, Bits imms, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + int datasize = (sf ? 64 : 32); + + /* if N != sf then UnallocatedEncoding(); */ + /* if sf == '0' && imms<5> == '1' then ReservedValue(); */ + + int lsb = (int)UInt(imms); + + /* Operation */ + Bits operand1 = X(datasize, n); + Bits operand2 = X(datasize, m); + Bits concat = Bits.Concat(operand1, operand2); + + Bits result = concat[lsb + datasize - 1, lsb]; + + X(d, result); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/lslv.xml + public static void Lslv(bool sf, Bits Rm, Bits Rn, Bits Rd) + { + Bits op2 = "00"; + + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + int datasize = (sf ? 64 : 32); + + ShiftType shift_type = DecodeShift(op2); + + /* Operation */ + Bits operand2 = X(datasize, m); + + Bits result = ShiftReg(datasize, n, shift_type, (int)(UInt(operand2) % datasize)); // BigInteger.Modulus Operator (BigInteger, BigInteger) + + X(d, result); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/lsrv.xml + public static void Lsrv(bool sf, Bits Rm, Bits Rn, Bits Rd) + { + Bits op2 = "01"; + + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + int datasize = (sf ? 64 : 32); + + ShiftType shift_type = DecodeShift(op2); + + /* Operation */ + Bits operand2 = X(datasize, m); + + Bits result = ShiftReg(datasize, n, shift_type, (int)(UInt(operand2) % datasize)); // BigInteger.Modulus Operator (BigInteger, BigInteger) + + X(d, result); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/orn_log_shift.xml + public static void Orn(bool sf, Bits shift, Bits Rm, Bits imm6, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + int datasize = (sf ? 64 : 32); + + /* if sf == '0' && imm6<5> == '1' then ReservedValue(); */ + + ShiftType shift_type = DecodeShift(shift); + int shift_amount = (int)UInt(imm6); + + /* Operation */ + Bits operand1 = X(datasize, n); + Bits operand2 = ShiftReg(datasize, m, shift_type, shift_amount); + + operand2 = NOT(operand2); + + Bits result = OR(operand1, operand2); + + X(d, result); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/orr_log_shift.xml + public static void Orr_Rs(bool sf, Bits shift, Bits Rm, Bits imm6, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + int datasize = (sf ? 64 : 32); + + /* if sf == '0' && imm6<5> == '1' then ReservedValue(); */ + + ShiftType shift_type = DecodeShift(shift); + int shift_amount = (int)UInt(imm6); + + /* Operation */ + Bits operand1 = X(datasize, n); + Bits operand2 = ShiftReg(datasize, m, shift_type, shift_amount); + + Bits result = OR(operand1, operand2); + + X(d, result); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/rorv.xml + public static void Rorv(bool sf, Bits Rm, Bits Rn, Bits Rd) + { + Bits op2 = "11"; + + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + int datasize = (sf ? 64 : 32); + + ShiftType shift_type = DecodeShift(op2); + + /* Operation */ + Bits operand2 = X(datasize, m); + + Bits result = ShiftReg(datasize, n, shift_type, (int)(UInt(operand2) % datasize)); // BigInteger.Modulus Operator (BigInteger, BigInteger) + + X(d, result); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/sbc.xml + public static void Sbc(bool sf, Bits Rm, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + int datasize = (sf ? 64 : 32); + + /* Operation */ + Bits result; + Bits operand1 = X(datasize, n); + Bits operand2 = X(datasize, m); + + operand2 = NOT(operand2); + + (result, _) = AddWithCarry(datasize, operand1, operand2, PSTATE.C); + + X(d, result); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/sbcs.xml + public static void Sbcs(bool sf, Bits Rm, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + int datasize = (sf ? 64 : 32); + + /* Operation */ + Bits result; + Bits operand1 = X(datasize, n); + Bits operand2 = X(datasize, m); + Bits nzcv; + + operand2 = NOT(operand2); + + (result, nzcv) = AddWithCarry(datasize, operand1, operand2, PSTATE.C); + + PSTATE.NZCV(nzcv); + + X(d, result); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/sdiv.xml + public static void Sdiv(bool sf, Bits Rm, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + int datasize = (sf ? 64 : 32); + + /* Operation */ + BigInteger result; + Bits operand1 = X(datasize, n); + Bits operand2 = X(datasize, m); + + if (IsZero(operand2)) + { + result = (BigInteger)0m; + } + else + { + result = RoundTowardsZero(Real(Int(operand1, false)) / Real(Int(operand2, false))); + } + + X(d, result.SubBigInteger(datasize - 1, 0)); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/sub_addsub_shift.xml + public static void Sub_Rs(bool sf, Bits shift, Bits Rm, Bits imm6, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + int datasize = (sf ? 64 : 32); + + /* if shift == '11' then ReservedValue(); */ + /* if sf == '0' && imm6<5> == '1' then ReservedValue(); */ + + ShiftType shift_type = DecodeShift(shift); + int shift_amount = (int)UInt(imm6); + + /* Operation */ + Bits result; + Bits operand1 = X(datasize, n); + Bits operand2 = ShiftReg(datasize, m, shift_type, shift_amount); + + operand2 = NOT(operand2); + + (result, _) = AddWithCarry(datasize, operand1, operand2, true); + + X(d, result); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/subs_addsub_shift.xml + public static void Subs_Rs(bool sf, Bits shift, Bits Rm, Bits imm6, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + int datasize = (sf ? 64 : 32); + + /* if shift == '11' then ReservedValue(); */ + /* if sf == '0' && imm6<5> == '1' then ReservedValue(); */ + + ShiftType shift_type = DecodeShift(shift); + int shift_amount = (int)UInt(imm6); + + /* Operation */ + Bits result; + Bits operand1 = X(datasize, n); + Bits operand2 = ShiftReg(datasize, m, shift_type, shift_amount); + Bits nzcv; + + operand2 = NOT(operand2); + + (result, nzcv) = AddWithCarry(datasize, operand1, operand2, true); + + PSTATE.NZCV(nzcv); + + X(d, result); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/udiv.xml + public static void Udiv(bool sf, Bits Rm, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + int datasize = (sf ? 64 : 32); + + /* Operation */ + BigInteger result; + Bits operand1 = X(datasize, n); + Bits operand2 = X(datasize, m); + + if (IsZero(operand2)) + { + result = (BigInteger)0m; + } + else + { + result = RoundTowardsZero(Real(Int(operand1, true)) / Real(Int(operand2, true))); + } + + X(d, result.SubBigInteger(datasize - 1, 0)); + } +#endregion + +#region "AluRx" + // https://meriac.github.io/archex/A64_v83A_ISA/add_addsub_ext.xml + public static void Add_Rx(bool sf, Bits Rm, Bits option, Bits imm3, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + int datasize = (sf ? 64 : 32); + + ExtendType extend_type = DecodeRegExtend(option); + int shift = (int)UInt(imm3); + + /* if shift > 4 then ReservedValue(); */ + + /* Operation */ + Bits result; + Bits operand1 = (n == 31 ? SP(datasize) : X(datasize, n)); + Bits operand2 = ExtendReg(datasize, m, extend_type, shift); + + (result, _) = AddWithCarry(datasize, operand1, operand2, false); + + if (d == 31) + { + SP(result); + } + else + { + X(d, result); + } + } + + // https://meriac.github.io/archex/A64_v83A_ISA/adds_addsub_ext.xml + public static void Adds_Rx(bool sf, Bits Rm, Bits option, Bits imm3, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + int datasize = (sf ? 64 : 32); + + ExtendType extend_type = DecodeRegExtend(option); + int shift = (int)UInt(imm3); + + /* if shift > 4 then ReservedValue(); */ + + /* Operation */ + Bits result; + Bits operand1 = (n == 31 ? SP(datasize) : X(datasize, n)); + Bits operand2 = ExtendReg(datasize, m, extend_type, shift); + Bits nzcv; + + (result, nzcv) = AddWithCarry(datasize, operand1, operand2, false); + + PSTATE.NZCV(nzcv); + + X(d, result); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/sub_addsub_ext.xml + public static void Sub_Rx(bool sf, Bits Rm, Bits option, Bits imm3, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + int datasize = (sf ? 64 : 32); + + ExtendType extend_type = DecodeRegExtend(option); + int shift = (int)UInt(imm3); + + /* if shift > 4 then ReservedValue(); */ + + /* Operation */ + Bits result; + Bits operand1 = (n == 31 ? SP(datasize) : X(datasize, n)); + Bits operand2 = ExtendReg(datasize, m, extend_type, shift); + + operand2 = NOT(operand2); + + (result, _) = AddWithCarry(datasize, operand1, operand2, true); + + if (d == 31) + { + SP(result); + } + else + { + X(d, result); + } + } + + // https://meriac.github.io/archex/A64_v83A_ISA/subs_addsub_ext.xml + public static void Subs_Rx(bool sf, Bits Rm, Bits option, Bits imm3, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + int datasize = (sf ? 64 : 32); + + ExtendType extend_type = DecodeRegExtend(option); + int shift = (int)UInt(imm3); + + /* if shift > 4 then ReservedValue(); */ + + /* Operation */ + Bits result; + Bits operand1 = (n == 31 ? SP(datasize) : X(datasize, n)); + Bits operand2 = ExtendReg(datasize, m, extend_type, shift); + Bits nzcv; + + operand2 = NOT(operand2); + + (result, nzcv) = AddWithCarry(datasize, operand1, operand2, true); + + PSTATE.NZCV(nzcv); + + X(d, result); + } +#endregion + +#region "Bfm" + // https://meriac.github.io/archex/A64_v83A_ISA/bfm.xml + public static void Bfm(bool sf, bool N, Bits immr, Bits imms, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int datasize = (sf ? 64 : 32); + + int R; + Bits wmask; + Bits tmask; + + /* if sf == '1' && N != '1' then ReservedValue(); */ + /* if sf == '0' && (N != '0' || immr<5> != '0' || imms<5> != '0') then ReservedValue(); */ + + R = (int)UInt(immr); + (wmask, tmask) = DecodeBitMasks(datasize, N, imms, immr, false); + + /* Operation */ + Bits dst = X(datasize, d); + Bits src = X(datasize, n); + + // perform bitfield move on low bits + Bits bot = OR(AND(dst, NOT(wmask)), AND(ROR(src, R), wmask)); + + // combine extension bits and result bits + X(d, OR(AND(dst, NOT(tmask)), AND(bot, tmask))); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/sbfm.xml + public static void Sbfm(bool sf, bool N, Bits immr, Bits imms, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int datasize = (sf ? 64 : 32); + + int R; + int S; + Bits wmask; + Bits tmask; + + /* if sf == '1' && N != '1' then ReservedValue(); */ + /* if sf == '0' && (N != '0' || immr<5> != '0' || imms<5> != '0') then ReservedValue(); */ + + R = (int)UInt(immr); + S = (int)UInt(imms); + (wmask, tmask) = DecodeBitMasks(datasize, N, imms, immr, false); + + /* Operation */ + Bits src = X(datasize, n); + + // perform bitfield move on low bits + Bits bot = AND(ROR(src, R), wmask); + + // determine extension bits (sign, zero or dest register) + Bits top = Replicate(datasize, src[S]); + + // combine extension bits and result bits + X(d, OR(AND(top, NOT(tmask)), AND(bot, tmask))); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/ubfm.xml + public static void Ubfm(bool sf, bool N, Bits immr, Bits imms, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int datasize = (sf ? 64 : 32); + + int R; + Bits wmask; + Bits tmask; + + /* if sf == '1' && N != '1' then ReservedValue(); */ + /* if sf == '0' && (N != '0' || immr<5> != '0' || imms<5> != '0') then ReservedValue(); */ + + R = (int)UInt(immr); + (wmask, tmask) = DecodeBitMasks(datasize, N, imms, immr, false); + + /* Operation */ + Bits src = X(datasize, n); + + // perform bitfield move on low bits + Bits bot = AND(ROR(src, R), wmask); + + // combine extension bits and result bits + X(d, AND(bot, tmask)); + } +#endregion + +#region "CcmpImm" + // https://meriac.github.io/archex/A64_v83A_ISA/ccmn_imm.xml + public static void Ccmn_Imm(bool sf, Bits imm5, Bits cond, Bits Rn, Bits nzcv) + { + /* Decode */ + int n = (int)UInt(Rn); + int datasize = (sf ? 64 : 32); + + Bits flags = nzcv; + Bits imm = ZeroExtend(imm5, datasize); + + /* Operation */ + Bits operand1 = X(datasize, n); + + if (ConditionHolds(cond)) + { + (_, flags) = AddWithCarry(datasize, operand1, imm, false); + } + + PSTATE.NZCV(flags); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/ccmp_imm.xml + public static void Ccmp_Imm(bool sf, Bits imm5, Bits cond, Bits Rn, Bits nzcv) + { + /* Decode */ + int n = (int)UInt(Rn); + int datasize = (sf ? 64 : 32); + + Bits flags = nzcv; + Bits imm = ZeroExtend(imm5, datasize); + + /* Operation */ + Bits operand1 = X(datasize, n); + Bits operand2; + + if (ConditionHolds(cond)) + { + operand2 = NOT(imm); + (_, flags) = AddWithCarry(datasize, operand1, operand2, true); + } + + PSTATE.NZCV(flags); + } +#endregion + +#region "CcmpReg" + // https://meriac.github.io/archex/A64_v83A_ISA/ccmn_reg.xml + public static void Ccmn_Reg(bool sf, Bits Rm, Bits cond, Bits Rn, Bits nzcv) + { + /* Decode */ + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + int datasize = (sf ? 64 : 32); + + Bits flags = nzcv; + + /* Operation */ + Bits operand1 = X(datasize, n); + Bits operand2 = X(datasize, m); + + if (ConditionHolds(cond)) + { + (_, flags) = AddWithCarry(datasize, operand1, operand2, false); + } + + PSTATE.NZCV(flags); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/ccmp_reg.xml + public static void Ccmp_Reg(bool sf, Bits Rm, Bits cond, Bits Rn, Bits nzcv) + { + /* Decode */ + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + int datasize = (sf ? 64 : 32); + + Bits flags = nzcv; + + /* Operation */ + Bits operand1 = X(datasize, n); + Bits operand2 = X(datasize, m); + + if (ConditionHolds(cond)) + { + operand2 = NOT(operand2); + (_, flags) = AddWithCarry(datasize, operand1, operand2, true); + } + + PSTATE.NZCV(flags); + } +#endregion + +#region "Csel" + // https://meriac.github.io/archex/A64_v83A_ISA/csel.xml + public static void Csel(bool sf, Bits Rm, Bits cond, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + int datasize = (sf ? 64 : 32); + + /* Operation */ + Bits result; + Bits operand1 = X(datasize, n); + Bits operand2 = X(datasize, m); + + if (ConditionHolds(cond)) + { + result = operand1; + } + else + { + result = operand2; + } + + X(d, result); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/csinc.xml + public static void Csinc(bool sf, Bits Rm, Bits cond, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + int datasize = (sf ? 64 : 32); + + /* Operation */ + Bits result; + Bits operand1 = X(datasize, n); + Bits operand2 = X(datasize, m); + + if (ConditionHolds(cond)) + { + result = operand1; + } + else + { + result = operand2 + 1; + } + + X(d, result); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/csinv.xml + public static void Csinv(bool sf, Bits Rm, Bits cond, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + int datasize = (sf ? 64 : 32); + + /* Operation */ + Bits result; + Bits operand1 = X(datasize, n); + Bits operand2 = X(datasize, m); + + if (ConditionHolds(cond)) + { + result = operand1; + } + else + { + result = NOT(operand2); + } + + X(d, result); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/csneg.xml + public static void Csneg(bool sf, Bits Rm, Bits cond, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + int datasize = (sf ? 64 : 32); + + /* Operation */ + Bits result; + Bits operand1 = X(datasize, n); + Bits operand2 = X(datasize, m); + + if (ConditionHolds(cond)) + { + result = operand1; + } + else + { + result = NOT(operand2); + result = result + 1; + } + + X(d, result); + } +#endregion + +#region "Mov" + // https://meriac.github.io/archex/A64_v83A_ISA/movk.xml + public static void Movk(bool sf, Bits hw, Bits imm16, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int datasize = (sf ? 64 : 32); + + /* if sf == '0' && hw<1> == '1' then UnallocatedEncoding(); */ + + int pos = (int)UInt(Bits.Concat(hw, "0000")); + + /* Operation */ + Bits result = X(datasize, d); + + result[pos + 15, pos] = imm16; + + X(d, result); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/movn.xml + public static void Movn(bool sf, Bits hw, Bits imm16, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int datasize = (sf ? 64 : 32); + + /* if sf == '0' && hw<1> == '1' then UnallocatedEncoding(); */ + + int pos = (int)UInt(Bits.Concat(hw, "0000")); + + /* Operation */ + Bits result = Zeros(datasize); + + result[pos + 15, pos] = imm16; + result = NOT(result); + + X(d, result); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/movz.xml + public static void Movz(bool sf, Bits hw, Bits imm16, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int datasize = (sf ? 64 : 32); + + /* if sf == '0' && hw<1> == '1' then UnallocatedEncoding(); */ + + int pos = (int)UInt(Bits.Concat(hw, "0000")); + + /* Operation */ + Bits result = Zeros(datasize); + + result[pos + 15, pos] = imm16; + + X(d, result); + } +#endregion + +#region "Mul" + // https://meriac.github.io/archex/A64_v83A_ISA/madd.xml + public static void Madd(bool sf, Bits Rm, Bits Ra, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + int a = (int)UInt(Ra); + int datasize = (sf ? 64 : 32); + + /* Operation */ + Bits operand1 = X(datasize, n); + Bits operand2 = X(datasize, m); + Bits operand3 = X(datasize, a); + + BigInteger result = UInt(operand3) + (UInt(operand1) * UInt(operand2)); + + X(d, result.SubBigInteger(datasize - 1, 0)); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/msub.xml + public static void Msub(bool sf, Bits Rm, Bits Ra, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + int a = (int)UInt(Ra); + int datasize = (sf ? 64 : 32); + + /* Operation */ + Bits operand1 = X(datasize, n); + Bits operand2 = X(datasize, m); + Bits operand3 = X(datasize, a); + + BigInteger result = UInt(operand3) - (UInt(operand1) * UInt(operand2)); + + X(d, result.SubBigInteger(datasize - 1, 0)); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/smaddl.xml + public static void Smaddl(Bits Rm, Bits Ra, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + int a = (int)UInt(Ra); + + /* Operation */ + Bits operand1 = X(32, n); + Bits operand2 = X(32, m); + Bits operand3 = X(64, a); + + BigInteger result = Int(operand3, false) + (Int(operand1, false) * Int(operand2, false)); + + X(d, result.SubBigInteger(63, 0)); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/umaddl.xml + public static void Umaddl(Bits Rm, Bits Ra, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + int a = (int)UInt(Ra); + + /* Operation */ + Bits operand1 = X(32, n); + Bits operand2 = X(32, m); + Bits operand3 = X(64, a); + + BigInteger result = Int(operand3, true) + (Int(operand1, true) * Int(operand2, true)); + + X(d, result.SubBigInteger(63, 0)); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/smsubl.xml + public static void Smsubl(Bits Rm, Bits Ra, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + int a = (int)UInt(Ra); + + /* Operation */ + Bits operand1 = X(32, n); + Bits operand2 = X(32, m); + Bits operand3 = X(64, a); + + BigInteger result = Int(operand3, false) - (Int(operand1, false) * Int(operand2, false)); + + X(d, result.SubBigInteger(63, 0)); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/umsubl.xml + public static void Umsubl(Bits Rm, Bits Ra, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + int a = (int)UInt(Ra); + + /* Operation */ + Bits operand1 = X(32, n); + Bits operand2 = X(32, m); + Bits operand3 = X(64, a); + + BigInteger result = Int(operand3, true) - (Int(operand1, true) * Int(operand2, true)); + + X(d, result.SubBigInteger(63, 0)); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/smulh.xml + public static void Smulh(Bits Rm, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + + /* Operation */ + Bits operand1 = X(64, n); + Bits operand2 = X(64, m); + + BigInteger result = Int(operand1, false) * Int(operand2, false); + + X(d, result.SubBigInteger(127, 64)); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/umulh.xml + public static void Umulh(Bits Rm, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + + /* Operation */ + Bits operand1 = X(64, n); + Bits operand2 = X(64, m); + + BigInteger result = Int(operand1, true) * Int(operand2, true); + + X(d, result.SubBigInteger(127, 64)); + } +#endregion + } + + internal static class SimdFp + { +#region "Simd" + // https://meriac.github.io/archex/A64_v83A_ISA/abs_advsimd.xml#ABS_asisdmisc_R + public static void Abs_S(Bits size, Bits Rn, Bits Rd) + { + bool U = false; + + /* Decode Scalar */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + + /* if size != '11' then ReservedValue(); */ + + int esize = 8 << (int)UInt(size); + int datasize = esize; + int elements = 1; + + bool neg = (U == true); + + /* Operation */ + /* CheckFPAdvSIMDEnabled64(); */ + + Bits result = new Bits(datasize); + Bits operand = V(datasize, n); + BigInteger element; + + for (int e = 0; e <= elements - 1; e++) + { + element = SInt(Elem(operand, e, esize)); + + if (neg) + { + element = -element; + } + else + { + element = Abs(element); + } + + Elem(result, e, esize, element.SubBigInteger(esize - 1, 0)); + } + + V(d, result); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/abs_advsimd.xml#ABS_asimdmisc_R + public static void Abs_V(bool Q, Bits size, Bits Rn, Bits Rd) + { + bool U = false; + + /* Decode Vector */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + + /* if size:Q == '110' then ReservedValue(); */ + + int esize = 8 << (int)UInt(size); + int datasize = (Q ? 128 : 64); + int elements = datasize / esize; + + bool neg = (U == true); + + /* Operation */ + /* CheckFPAdvSIMDEnabled64(); */ + + Bits result = new Bits(datasize); + Bits operand = V(datasize, n); + BigInteger element; + + for (int e = 0; e <= elements - 1; e++) + { + element = SInt(Elem(operand, e, esize)); + + if (neg) + { + element = -element; + } + else + { + element = Abs(element); + } + + Elem(result, e, esize, element.SubBigInteger(esize - 1, 0)); + } + + V(d, result); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/addp_advsimd_pair.xml + public static void Addp_S(Bits size, Bits Rn, Bits Rd) + { + /* Decode Scalar */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + + /* if size != '11' then ReservedValue(); */ + + int esize = 8 << (int)UInt(size); + int datasize = esize * 2; + // int elements = 2; + + ReduceOp op = ReduceOp.ReduceOp_ADD; + + /* Operation */ + /* CheckFPAdvSIMDEnabled64(); */ + + Bits operand = V(datasize, n); + + V(d, Reduce(op, operand, esize)); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/addv_advsimd.xml + public static void Addv_V(bool Q, Bits size, Bits Rn, Bits Rd) + { + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + + /* if size:Q == '100' then ReservedValue(); */ + /* if size == '11' then ReservedValue(); */ + + int esize = 8 << (int)UInt(size); + int datasize = (Q ? 128 : 64); + // int elements = datasize / esize; + + ReduceOp op = ReduceOp.ReduceOp_ADD; + + /* Operation */ + /* CheckFPAdvSIMDEnabled64(); */ + + Bits operand = V(datasize, n); + + V(d, Reduce(op, operand, esize)); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/neg_advsimd.xml#NEG_asisdmisc_R + public static void Neg_S(Bits size, Bits Rn, Bits Rd) + { + bool U = true; + + /* Decode Scalar */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + + /* if size != '11' then ReservedValue(); */ + + int esize = 8 << (int)UInt(size); + int datasize = esize; + int elements = 1; + + bool neg = (U == true); + + /* Operation */ + /* CheckFPAdvSIMDEnabled64(); */ + + Bits result = new Bits(datasize); + Bits operand = V(datasize, n); + BigInteger element; + + for (int e = 0; e <= elements - 1; e++) + { + element = SInt(Elem(operand, e, esize)); + + if (neg) + { + element = -element; + } + else + { + element = Abs(element); + } + + Elem(result, e, esize, element.SubBigInteger(esize - 1, 0)); + } + + V(d, result); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/neg_advsimd.xml#NEG_asimdmisc_R + public static void Neg_V(bool Q, Bits size, Bits Rn, Bits Rd) + { + bool U = true; + + /* Decode Vector */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + + /* if size:Q == '110' then ReservedValue(); */ + + int esize = 8 << (int)UInt(size); + int datasize = (Q ? 128 : 64); + int elements = datasize / esize; + + bool neg = (U == true); + + /* Operation */ + /* CheckFPAdvSIMDEnabled64(); */ + + Bits result = new Bits(datasize); + Bits operand = V(datasize, n); + BigInteger element; + + for (int e = 0; e <= elements - 1; e++) + { + element = SInt(Elem(operand, e, esize)); + + if (neg) + { + element = -element; + } + else + { + element = Abs(element); + } + + Elem(result, e, esize, element.SubBigInteger(esize - 1, 0)); + } + + V(d, result); + } +#endregion + +#region "SimdReg" + // https://meriac.github.io/archex/A64_v83A_ISA/add_advsimd.xml#ADD_asisdsame_only + public static void Add_S(Bits size, Bits Rm, Bits Rn, Bits Rd) + { + bool U = false; + + /* Decode Scalar */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + + /* if size != '11' then ReservedValue(); */ + + int esize = 8 << (int)UInt(size); + int datasize = esize; + int elements = 1; + + bool sub_op = (U == true); + + /* Operation */ + /* CheckFPAdvSIMDEnabled64(); */ + + Bits result = new Bits(datasize); + Bits operand1 = V(datasize, n); + Bits operand2 = V(datasize, m); + Bits element1; + Bits element2; + + for (int e = 0; e <= elements - 1; e++) + { + element1 = Elem(operand1, e, esize); + element2 = Elem(operand2, e, esize); + + if (sub_op) + { + Elem(result, e, esize, element1 - element2); + } + else + { + Elem(result, e, esize, element1 + element2); + } + } + + V(d, result); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/add_advsimd.xml#ADD_asimdsame_only + public static void Add_V(bool Q, Bits size, Bits Rm, Bits Rn, Bits Rd) + { + bool U = false; + + /* Decode Vector */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + + /* if size:Q == '110' then ReservedValue(); */ + + int esize = 8 << (int)UInt(size); + int datasize = (Q ? 128 : 64); + int elements = datasize / esize; + + bool sub_op = (U == true); + + /* Operation */ + /* CheckFPAdvSIMDEnabled64(); */ + + Bits result = new Bits(datasize); + Bits operand1 = V(datasize, n); + Bits operand2 = V(datasize, m); + Bits element1; + Bits element2; + + for (int e = 0; e <= elements - 1; e++) + { + element1 = Elem(operand1, e, esize); + element2 = Elem(operand2, e, esize); + + if (sub_op) + { + Elem(result, e, esize, element1 - element2); + } + else + { + Elem(result, e, esize, element1 + element2); + } + } + + V(d, result); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/addhn_advsimd.xml + public static void Addhn_V(bool Q, Bits size, Bits Rm, Bits Rn, Bits Rd) + { + bool U = false; + bool o1 = false; + + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + + /* if size == '11' then ReservedValue(); */ + + int esize = 8 << (int)UInt(size); + int datasize = 64; + int part = (int)UInt(Q); + int elements = datasize / esize; + + bool sub_op = (o1 == true); + bool round = (U == true); + + /* Operation */ + /* CheckFPAdvSIMDEnabled64(); */ + + Bits result = new Bits(datasize); + Bits operand1 = V(2 * datasize, n); + Bits operand2 = V(2 * datasize, m); + BigInteger round_const = (round ? (BigInteger)1 << (esize - 1) : 0); + Bits sum; + Bits element1; + Bits element2; + + for (int e = 0; e <= elements - 1; e++) + { + element1 = Elem(operand1, e, 2 * esize); + element2 = Elem(operand2, e, 2 * esize); + + if (sub_op) + { + sum = element1 - element2; + } + else + { + sum = element1 + element2; + } + + sum = sum + round_const; + + Elem(result, e, esize, sum[2 * esize - 1, esize]); + } + + Vpart(d, part, result); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/addp_advsimd_vec.xml + public static void Addp_V(bool Q, Bits size, Bits Rm, Bits Rn, Bits Rd) + { + /* Decode Vector */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + + /* if size:Q == '110' then ReservedValue(); */ + + int esize = 8 << (int)UInt(size); + int datasize = (Q ? 128 : 64); + int elements = datasize / esize; + + /* Operation */ + /* CheckFPAdvSIMDEnabled64(); */ + + Bits result = new Bits(datasize); + Bits operand1 = V(datasize, n); + Bits operand2 = V(datasize, m); + Bits concat = Bits.Concat(operand2, operand1); + Bits element1; + Bits element2; + + for (int e = 0; e <= elements - 1; e++) + { + element1 = Elem(concat, 2 * e, esize); + element2 = Elem(concat, (2 * e) + 1, esize); + + Elem(result, e, esize, element1 + element2); + } + + V(d, result); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/raddhn_advsimd.xml + public static void Raddhn_V(bool Q, Bits size, Bits Rm, Bits Rn, Bits Rd) + { + bool U = true; + bool o1 = false; + + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + + /* if size == '11' then ReservedValue(); */ + + int esize = 8 << (int)UInt(size); + int datasize = 64; + int part = (int)UInt(Q); + int elements = datasize / esize; + + bool sub_op = (o1 == true); + bool round = (U == true); + + /* Operation */ + /* CheckFPAdvSIMDEnabled64(); */ + + Bits result = new Bits(datasize); + Bits operand1 = V(2 * datasize, n); + Bits operand2 = V(2 * datasize, m); + BigInteger round_const = (round ? (BigInteger)1 << (esize - 1) : 0); + Bits sum; + Bits element1; + Bits element2; + + for (int e = 0; e <= elements - 1; e++) + { + element1 = Elem(operand1, e, 2 * esize); + element2 = Elem(operand2, e, 2 * esize); + + if (sub_op) + { + sum = element1 - element2; + } + else + { + sum = element1 + element2; + } + + sum = sum + round_const; + + Elem(result, e, esize, sum[2 * esize - 1, esize]); + } + + Vpart(d, part, result); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/rsubhn_advsimd.xml + public static void Rsubhn_V(bool Q, Bits size, Bits Rm, Bits Rn, Bits Rd) + { + bool U = true; + bool o1 = true; + + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + + /* if size == '11' then ReservedValue(); */ + + int esize = 8 << (int)UInt(size); + int datasize = 64; + int part = (int)UInt(Q); + int elements = datasize / esize; + + bool sub_op = (o1 == true); + bool round = (U == true); + + /* Operation */ + /* CheckFPAdvSIMDEnabled64(); */ + + Bits result = new Bits(datasize); + Bits operand1 = V(2 * datasize, n); + Bits operand2 = V(2 * datasize, m); + BigInteger round_const = (round ? (BigInteger)1 << (esize - 1) : 0); + Bits sum; + Bits element1; + Bits element2; + + for (int e = 0; e <= elements - 1; e++) + { + element1 = Elem(operand1, e, 2 * esize); + element2 = Elem(operand2, e, 2 * esize); + + if (sub_op) + { + sum = element1 - element2; + } + else + { + sum = element1 + element2; + } + + sum = sum + round_const; + + Elem(result, e, esize, sum[2 * esize - 1, esize]); + } + + Vpart(d, part, result); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/sub_advsimd.xml#SUB_asisdsame_only + public static void Sub_S(Bits size, Bits Rm, Bits Rn, Bits Rd) + { + bool U = true; + + /* Decode Scalar */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + + /* if size != '11' then ReservedValue(); */ + + int esize = 8 << (int)UInt(size); + int datasize = esize; + int elements = 1; + + bool sub_op = (U == true); + + /* Operation */ + /* CheckFPAdvSIMDEnabled64(); */ + + Bits result = new Bits(datasize); + Bits operand1 = V(datasize, n); + Bits operand2 = V(datasize, m); + Bits element1; + Bits element2; + + for (int e = 0; e <= elements - 1; e++) + { + element1 = Elem(operand1, e, esize); + element2 = Elem(operand2, e, esize); + + if (sub_op) + { + Elem(result, e, esize, element1 - element2); + } + else + { + Elem(result, e, esize, element1 + element2); + } + } + + V(d, result); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/sub_advsimd.xml#SUB_asimdsame_only + public static void Sub_V(bool Q, Bits size, Bits Rm, Bits Rn, Bits Rd) + { + bool U = true; + + /* Decode Vector */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + + /* if size:Q == '110' then ReservedValue(); */ + + int esize = 8 << (int)UInt(size); + int datasize = (Q ? 128 : 64); + int elements = datasize / esize; + + bool sub_op = (U == true); + + /* Operation */ + /* CheckFPAdvSIMDEnabled64(); */ + + Bits result = new Bits(datasize); + Bits operand1 = V(datasize, n); + Bits operand2 = V(datasize, m); + Bits element1; + Bits element2; + + for (int e = 0; e <= elements - 1; e++) + { + element1 = Elem(operand1, e, esize); + element2 = Elem(operand2, e, esize); + + if (sub_op) + { + Elem(result, e, esize, element1 - element2); + } + else + { + Elem(result, e, esize, element1 + element2); + } + } + + V(d, result); + } + + // https://meriac.github.io/archex/A64_v83A_ISA/subhn_advsimd.xml + public static void Subhn_V(bool Q, Bits size, Bits Rm, Bits Rn, Bits Rd) + { + bool U = false; + bool o1 = true; + + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + + /* if size == '11' then ReservedValue(); */ + + int esize = 8 << (int)UInt(size); + int datasize = 64; + int part = (int)UInt(Q); + int elements = datasize / esize; + + bool sub_op = (o1 == true); + bool round = (U == true); + + /* Operation */ + /* CheckFPAdvSIMDEnabled64(); */ + + Bits result = new Bits(datasize); + Bits operand1 = V(2 * datasize, n); + Bits operand2 = V(2 * datasize, m); + BigInteger round_const = (round ? (BigInteger)1 << (esize - 1) : 0); + Bits sum; + Bits element1; + Bits element2; + + for (int e = 0; e <= elements - 1; e++) + { + element1 = Elem(operand1, e, 2 * esize); + element2 = Elem(operand2, e, 2 * esize); + + if (sub_op) + { + sum = element1 - element2; + } + else + { + sum = element1 + element2; + } + + sum = sum + round_const; + + Elem(result, e, esize, sum[2 * esize - 1, esize]); + } + + Vpart(d, part, result); + } +#endregion + } +} diff --git a/Ryujinx.Tests/Cpu/Tester/Pseudocode.cs b/Ryujinx.Tests/Cpu/Tester/Pseudocode.cs new file mode 100644 index 000000000..cfe8aa3d6 --- /dev/null +++ b/Ryujinx.Tests/Cpu/Tester/Pseudocode.cs @@ -0,0 +1,1112 @@ +// https://github.com/LDj3SNuD/ARM_v8-A_AArch64_Instructions_Tester/blob/master/Tester/Pseudocode.cs + +// https://meriac.github.io/archex/A64_v83A_ISA/shared_pseudocode.xml +// https://alastairreid.github.io/asl-lexical-syntax/ + +// | ------------------------|----------------------------------- | +// | ASL | C# | +// | ------------------------|----------------------------------- | +// | bit, bits(1); boolean | bool | +// | bits | Bits | +// | integer | BigInteger, int | +// | real | decimal | +// | ------------------------|----------------------------------- | +// | '0'; FALSE | false | +// | '1'; TRUE | true | +// | '010' | "010" | +// | bitsX IN {bitsY, bitsZ} | (bitsX == bitsY || bitsX == bitsZ) | +// | DIV | / | +// | MOD | % | +// | ------------------------|----------------------------------- | + +using System; +using System.Numerics; + +namespace Ryujinx.Tests.Cpu.Tester +{ + using Types; + + using static Shared; + + internal static class AArch64 + { +#region "exceptions/exceptions/" + /* #AArch64.ResetControlRegisters.1 */ + public static void ResetControlRegisters(bool cold_reset) + { + PSTATE.N = cold_reset; + PSTATE.Z = cold_reset; + PSTATE.C = cold_reset; + PSTATE.V = cold_reset; + } + + /* */ + public static void TakeReset(bool cold_reset) + { + /* assert !HighestELUsingAArch32(); */ + + // Enter the highest implemented Exception level in AArch64 state + if (HaveEL(EL3)) + { + PSTATE.EL = EL3; + } + else if (HaveEL(EL2)) + { + PSTATE.EL = EL2; + } + else + { + PSTATE.EL = EL1; + } + + // Reset the system registers and other system components + AArch64.ResetControlRegisters(cold_reset); + + // Reset all other PSTATE fields + PSTATE.SP = true; // Select stack pointer + + // All registers, bits and fields not reset by the above pseudocode or by the BranchTo() call + // below are UNKNOWN bitstrings after reset. In particular, the return information registers + // ELR_ELx and SPSR_ELx have UNKNOWN values, so that it + // is impossible to return from a reset in an architecturally defined way. + AArch64.ResetGeneralRegisters(); + AArch64.ResetSIMDFPRegisters(); + AArch64.ResetSpecialRegisters(); + } +#endregion + +#region "functions/registers/" + /* #AArch64.ResetGeneralRegisters.0 */ + public static void ResetGeneralRegisters() + { + for (int i = 0; i <= 30; i++) + { + /* X[i] = bits(64) UNKNOWN; */ + _R[i].SetAll(false); + } + } + + /* #AArch64.ResetSIMDFPRegisters.0 */ + public static void ResetSIMDFPRegisters() + { + for (int i = 0; i <= 31; i++) + { + /* V[i] = bits(128) UNKNOWN; */ + _V[i].SetAll(false); + } + } + + /* #AArch64.ResetSpecialRegisters.0 */ + public static void ResetSpecialRegisters() + { + // AArch64 special registers + /* SP_EL0 = bits(64) UNKNOWN; */ + SP_EL0.SetAll(false); + /* SP_EL1 = bits(64) UNKNOWN; */ + SP_EL1.SetAll(false); + } + + // #impl-aarch64.SP.write.0 + public static void SP(Bits value) + { + /* int width = value.Count; */ + + /* assert width IN {32,64}; */ + + if (!PSTATE.SP) + { + SP_EL0 = ZeroExtend(64, value); + } + else + { + switch (PSTATE.EL) + { + case Bits bits when bits == EL0: + SP_EL0 = ZeroExtend(64, value); + break; + default: + case Bits bits when bits == EL1: + SP_EL1 = ZeroExtend(64, value); + break;/* + case Bits bits when bits == EL2: + SP_EL2 = ZeroExtend(64, value); + break; + case Bits bits when bits == EL3: + SP_EL3 = ZeroExtend(64, value); + break;*/ + } + } + } + + // #impl-aarch64.SP.read.0 + public static Bits SP(int width) + { + /* assert width IN {8,16,32,64}; */ + + if (!PSTATE.SP) + { + return SP_EL0[width - 1, 0]; + } + else + { + switch (PSTATE.EL) + { + case Bits bits when bits == EL0: + return SP_EL0[width - 1, 0]; + default: + case Bits bits when bits == EL1: + return SP_EL1[width - 1, 0];/* + case Bits bits when bits == EL2: + return SP_EL2[width - 1, 0]; + case Bits bits when bits == EL3: + return SP_EL3[width - 1, 0];*/ + } + } + } + + // #impl-aarch64.V.write.1 + public static void V(int n, Bits value) + { + /* int width = value.Count; */ + + /* assert n >= 0 && n <= 31; */ + /* assert width IN {8,16,32,64,128}; */ + + _V[n] = ZeroExtend(128, value); + } + + /* #impl-aarch64.V.read.1 */ + public static Bits V(int width, int n) + { + /* assert n >= 0 && n <= 31; */ + /* assert width IN {8,16,32,64,128}; */ + + return _V[n][width - 1, 0]; + } + + /* #impl-aarch64.Vpart.read.2 */ + public static Bits Vpart(int width, int n, int part) + { + /* assert n >= 0 && n <= 31; */ + /* assert part IN {0, 1}; */ + + if (part == 0) + { + /* assert width IN {8,16,32,64}; */ + return _V[n][width - 1, 0]; + } + else + { + /* assert width == 64; */ + return _V[n][(width * 2) - 1, width]; + } + } + + // #impl-aarch64.Vpart.write.2 + public static void Vpart(int n, int part, Bits value) + { + int width = value.Count; + + /* assert n >= 0 && n <= 31; */ + /* assert part IN {0, 1}; */ + + if (part == 0) + { + /* assert width IN {8,16,32,64}; */ + _V[n] = ZeroExtend(128, value); + } + else + { + /* assert width == 64; */ + _V[n][(width * 2) - 1, width] = value[width - 1, 0]; + } + } + + // #impl-aarch64.X.write.1 + public static void X(int n, Bits value) + { + /* int width = value.Count; */ + + /* assert n >= 0 && n <= 31; */ + /* assert width IN {32,64}; */ + + if (n != 31) + { + _R[n] = ZeroExtend(64, value); + } + } + + /* #impl-aarch64.X.read.1 */ + public static Bits X(int width, int n) + { + /* assert n >= 0 && n <= 31; */ + /* assert width IN {8,16,32,64}; */ + + if (n != 31) + { + return _R[n][width - 1, 0]; + } + else + { + return Zeros(width); + } + } +#endregion + +#region "instrs/extendreg/" + /* #impl-aarch64.DecodeRegExtend.1 */ + public static ExtendType DecodeRegExtend(Bits op) + { + switch (op) + { + default: + case Bits bits when bits == "000": + return ExtendType.ExtendType_UXTB; + case Bits bits when bits == "001": + return ExtendType.ExtendType_UXTH; + case Bits bits when bits == "010": + return ExtendType.ExtendType_UXTW; + case Bits bits when bits == "011": + return ExtendType.ExtendType_UXTX; + case Bits bits when bits == "100": + return ExtendType.ExtendType_SXTB; + case Bits bits when bits == "101": + return ExtendType.ExtendType_SXTH; + case Bits bits when bits == "110": + return ExtendType.ExtendType_SXTW; + case Bits bits when bits == "111": + return ExtendType.ExtendType_SXTX; + } + } + + /* #impl-aarch64.ExtendReg.3 */ + public static Bits ExtendReg(int N, int reg, ExtendType type, int shift) + { + /* assert shift >= 0 && shift <= 4; */ + + Bits val = X(N, reg); + bool unsigned; + int len; + + switch (type) + { + default: + case ExtendType.ExtendType_SXTB: + unsigned = false; len = 8; + break; + case ExtendType.ExtendType_SXTH: + unsigned = false; len = 16; + break; + case ExtendType.ExtendType_SXTW: + unsigned = false; len = 32; + break; + case ExtendType.ExtendType_SXTX: + unsigned = false; len = 64; + break; + case ExtendType.ExtendType_UXTB: + unsigned = true; len = 8; + break; + case ExtendType.ExtendType_UXTH: + unsigned = true; len = 16; + break; + case ExtendType.ExtendType_UXTW: + unsigned = true; len = 32; + break; + case ExtendType.ExtendType_UXTX: + unsigned = true; len = 64; + break; + } + + // Note the extended width of the intermediate value and + // that sign extension occurs from bit , not + // from bit . This is equivalent to the instruction + // [SU]BFIZ Rtmp, Rreg, #shift, #len + // It may also be seen as a sign/zero extend followed by a shift: + // LSL(Extend(val, N, unsigned), shift); + + len = Min(len, N - shift); + return Extend(Bits.Concat(val[len - 1, 0], Zeros(shift)), N, unsigned); + } + + // #ExtendType + public enum ExtendType {ExtendType_SXTB, ExtendType_SXTH, ExtendType_SXTW, ExtendType_SXTX, + ExtendType_UXTB, ExtendType_UXTH, ExtendType_UXTW, ExtendType_UXTX}; +#endregion + +#region "instrs/integer/bitmasks/" + /* #impl-aarch64.DecodeBitMasks.4 */ + public static (Bits, Bits) DecodeBitMasks(int M, bool immN, Bits imms, Bits immr, bool immediate) + { + Bits tmask, wmask; + Bits tmask_and, wmask_and; + Bits tmask_or, wmask_or; + Bits levels; + + // Compute log2 of element size + // 2^len must be in range [2, M] + int len = HighestSetBit(Bits.Concat(immN, NOT(imms))); + /* if len < 1 then ReservedValue(); */ + /* assert M >= (1 << len); */ + + // Determine S, R and S - R parameters + levels = ZeroExtend(Ones(len), 6); + + // For logical immediates an all-ones value of S is reserved + // since it would generate a useless all-ones result (many times) + /* if immediate && (imms AND levels) == levels then ReservedValue(); */ + + BigInteger S = UInt(AND(imms, levels)); + BigInteger R = UInt(AND(immr, levels)); + BigInteger diff = S - R; // 6-bit subtract with borrow + + // Compute "top mask" + tmask_and = OR(diff.SubBigInteger(5, 0), NOT(levels)); + tmask_or = AND(diff.SubBigInteger(5, 0), levels); + + tmask = Ones(64); + tmask = OR(AND(tmask, Replicate(Bits.Concat(Replicate(tmask_and[0], 1), Ones( 1)), 32)), Replicate(Bits.Concat(Zeros( 1), Replicate(tmask_or[0], 1)), 32)); + tmask = OR(AND(tmask, Replicate(Bits.Concat(Replicate(tmask_and[1], 2), Ones( 2)), 16)), Replicate(Bits.Concat(Zeros( 2), Replicate(tmask_or[1], 2)), 16)); + tmask = OR(AND(tmask, Replicate(Bits.Concat(Replicate(tmask_and[2], 4), Ones( 4)), 8)), Replicate(Bits.Concat(Zeros( 4), Replicate(tmask_or[2], 4)), 8)); + tmask = OR(AND(tmask, Replicate(Bits.Concat(Replicate(tmask_and[3], 8), Ones( 8)), 4)), Replicate(Bits.Concat(Zeros( 8), Replicate(tmask_or[3], 8)), 4)); + tmask = OR(AND(tmask, Replicate(Bits.Concat(Replicate(tmask_and[4], 16), Ones(16)), 2)), Replicate(Bits.Concat(Zeros(16), Replicate(tmask_or[4], 16)), 2)); + tmask = OR(AND(tmask, Replicate(Bits.Concat(Replicate(tmask_and[5], 32), Ones(32)), 1)), Replicate(Bits.Concat(Zeros(32), Replicate(tmask_or[5], 32)), 1)); + + // Compute "wraparound mask" + wmask_and = OR(immr, NOT(levels)); + wmask_or = AND(immr, levels); + + wmask = Zeros(64); + wmask = OR(AND(wmask, Replicate(Bits.Concat(Ones( 1), Replicate(wmask_and[0], 1)), 32)), Replicate(Bits.Concat(Replicate(wmask_or[0], 1), Zeros( 1)), 32)); + wmask = OR(AND(wmask, Replicate(Bits.Concat(Ones( 2), Replicate(wmask_and[1], 2)), 16)), Replicate(Bits.Concat(Replicate(wmask_or[1], 2), Zeros( 2)), 16)); + wmask = OR(AND(wmask, Replicate(Bits.Concat(Ones( 4), Replicate(wmask_and[2], 4)), 8)), Replicate(Bits.Concat(Replicate(wmask_or[2], 4), Zeros( 4)), 8)); + wmask = OR(AND(wmask, Replicate(Bits.Concat(Ones( 8), Replicate(wmask_and[3], 8)), 4)), Replicate(Bits.Concat(Replicate(wmask_or[3], 8), Zeros( 8)), 4)); + wmask = OR(AND(wmask, Replicate(Bits.Concat(Ones(16), Replicate(wmask_and[4], 16)), 2)), Replicate(Bits.Concat(Replicate(wmask_or[4], 16), Zeros(16)), 2)); + wmask = OR(AND(wmask, Replicate(Bits.Concat(Ones(32), Replicate(wmask_and[5], 32)), 1)), Replicate(Bits.Concat(Replicate(wmask_or[5], 32), Zeros(32)), 1)); + + if (diff.SubBigInteger(6)) // borrow from S - R + { + wmask = AND(wmask, tmask); + } + else + { + wmask = OR(wmask, tmask); + } + + return (wmask[M - 1, 0], tmask[M - 1, 0]); + } +#endregion + +#region "instrs/integer/shiftreg/" + /* #impl-aarch64.DecodeShift.1 */ + public static ShiftType DecodeShift(Bits op) + { + switch (op) + { + default: + case Bits bits when bits == "00": + return ShiftType.ShiftType_LSL; + case Bits bits when bits == "01": + return ShiftType.ShiftType_LSR; + case Bits bits when bits == "10": + return ShiftType.ShiftType_ASR; + case Bits bits when bits == "11": + return ShiftType.ShiftType_ROR; + } + } + + /* #impl-aarch64.ShiftReg.3 */ + public static Bits ShiftReg(int N, int reg, ShiftType type, int amount) + { + Bits result = X(N, reg); + + switch (type) + { + default: + case ShiftType.ShiftType_LSL: + result = LSL(result, amount); + break; + case ShiftType.ShiftType_LSR: + result = LSR(result, amount); + break; + case ShiftType.ShiftType_ASR: + result = ASR(result, amount); + break; + case ShiftType.ShiftType_ROR: + result = ROR(result, amount); + break; + } + + return result; + } + + // #ShiftType + public enum ShiftType {ShiftType_LSL, ShiftType_LSR, ShiftType_ASR, ShiftType_ROR}; +#endregion + +#region "instrs/vector/reduce/reduceop/" + public static Bits Reduce(ReduceOp op, Bits input, int esize) + { + int N = input.Count; + + int half; + Bits hi; + Bits lo; + Bits result = new Bits(esize); + + if (N == esize) + { + return new Bits(input); + } + + half = N / 2; + hi = Reduce(op, input[N - 1, half], esize); + lo = Reduce(op, input[half - 1, 0], esize); + + switch (op) + { + case ReduceOp.ReduceOp_FMINNUM: + /* result = FPMinNum(lo, hi, FPCR); */ + break; + case ReduceOp.ReduceOp_FMAXNUM: + /* result = FPMaxNum(lo, hi, FPCR); */ + break; + case ReduceOp.ReduceOp_FMIN: + /* result = FPMin(lo, hi, FPCR); */ + break; + case ReduceOp.ReduceOp_FMAX: + /* result = FPMax(lo, hi, FPCR); */ + break; + case ReduceOp.ReduceOp_FADD: + /* result = FPAdd(lo, hi, FPCR); */ + break; + default: + case ReduceOp.ReduceOp_ADD: + result = lo + hi; + break; + } + + return result; + } + + public enum ReduceOp {ReduceOp_FMINNUM, ReduceOp_FMAXNUM, + ReduceOp_FMIN, ReduceOp_FMAX, + ReduceOp_FADD, ReduceOp_ADD}; +#endregion + } + + internal static class Shared + { + static Shared() + { + _R = new Bits[31]; + for (int i = 0; i <= 30; i++) + { + _R[i] = new Bits(64, false); + } + + _V = new Bits[32]; + for (int i = 0; i <= 31; i++) + { + _V[i] = new Bits(128, false); + } + + SP_EL0 = new Bits(64, false); + SP_EL1 = new Bits(64, false); + + PSTATE.N = false; + PSTATE.Z = false; + PSTATE.C = false; + PSTATE.V = false; + PSTATE.EL = EL1; + PSTATE.SP = true; + } + +#region "functions/common/" + /* */ + public static Bits AND(Bits x, Bits y) + { + return x.And(y); + } + + // #impl-shared.ASR.2 + public static Bits ASR(Bits x, int shift) + { + int N = x.Count; + + /* assert shift >= 0; */ + + Bits result; + + if (shift == 0) + { + result = new Bits(x); + } + else + { + (result, _) = ASR_C(x, shift); + } + + return result; + } + + // #impl-shared.ASR_C.2 + public static (Bits, bool) ASR_C(Bits x, int shift) + { + int N = x.Count; + + /* assert shift > 0; */ + + Bits extended_x = SignExtend(x, shift + N); + Bits result = extended_x[shift + N - 1, shift]; + bool carry_out = extended_x[shift - 1]; + + return (result, carry_out); + } + + // #impl-shared.Abs.1 + public static BigInteger Abs(BigInteger x) + { + return (x >= 0 ? x : -x); + } + + // #impl-shared.CountLeadingSignBits.1 + public static int CountLeadingSignBits(Bits x) + { + int N = x.Count; + + return CountLeadingZeroBits(EOR(x[N - 1, 1], x[N - 2, 0])); + } + + // #impl-shared.CountLeadingZeroBits.1 + public static int CountLeadingZeroBits(Bits x) + { + int N = x.Count; + + return (N - 1 - HighestSetBit(x)); + } + + // #impl-shared.Elem.read.3 + public static Bits Elem(/*in */Bits vector, int e, int size) + { + /* int N = vector.Count; */ + + /* assert e >= 0 && (e+1)*size <= N; */ + + return vector[e * size + size - 1, e * size]; + } + + // #impl-shared.Elem.write.3 + public static void Elem(/*out */Bits vector, int e, int size, Bits value) + { + /* int N = vector.Count; */ + + /* assert e >= 0 && (e+1)*size <= N; */ + + vector[(e + 1) * size - 1, e * size] = value; + } + + /* */ + public static Bits EOR(Bits x, Bits y) + { + return x.Xor(y); + } + + // #impl-shared.Extend.3 + public static Bits Extend(Bits x, int N, bool unsigned) + { + if (unsigned) + { + return ZeroExtend(x, N); + } + else + { + return SignExtend(x, N); + } + } + + /* #impl-shared.Extend.2 */ + public static Bits Extend(int N, Bits x, bool unsigned) + { + return Extend(x, N, unsigned); + } + + // #impl-shared.HighestSetBit.1 + public static int HighestSetBit(Bits x) + { + int N = x.Count; + + for (int i = N - 1; i >= 0; i--) + { + if (x[i]) + { + return i; + } + } + + return -1; + } + + // #impl-shared.Int.2 + public static BigInteger Int(Bits x, bool unsigned) + { + return (unsigned ? UInt(x) : SInt(x)); + } + + // #impl-shared.IsOnes.1 + public static bool IsOnes(Bits x) + { + int N = x.Count; + + return (x == Ones(N)); + } + + // #impl-shared.IsZero.1 + public static bool IsZero(Bits x) + { + int N = x.Count; + + return (x == Zeros(N)); + } + + // #impl-shared.IsZeroBit.1 + public static bool IsZeroBit(Bits x) + { + return IsZero(x); + } + + // #impl-shared.LSL.2 + public static Bits LSL(Bits x, int shift) + { + int N = x.Count; + + /* assert shift >= 0; */ + + Bits result; + + if (shift == 0) + { + result = new Bits(x); + } + else + { + (result, _) = LSL_C(x, shift); + } + + return result; + } + + // #impl-shared.LSL_C.2 + public static (Bits, bool) LSL_C(Bits x, int shift) + { + int N = x.Count; + + /* assert shift > 0; */ + + Bits extended_x = Bits.Concat(x, Zeros(shift)); + Bits result = extended_x[N - 1, 0]; + bool carry_out = extended_x[N]; + + return (result, carry_out); + } + + // #impl-shared.LSR.2 + public static Bits LSR(Bits x, int shift) + { + int N = x.Count; + + /* assert shift >= 0; */ + + Bits result; + + if (shift == 0) + { + result = new Bits(x); + } + else + { + (result, _) = LSR_C(x, shift); + } + + return result; + } + + // #impl-shared.LSR_C.2 + public static (Bits, bool) LSR_C(Bits x, int shift) + { + int N = x.Count; + + /* assert shift > 0; */ + + Bits extended_x = ZeroExtend(x, shift + N); + Bits result = extended_x[shift + N - 1, shift]; + bool carry_out = extended_x[shift - 1]; + + return (result, carry_out); + } + + // #impl-shared.Min.2 + public static int Min(int a, int b) + { + if (a <= b) + { + return a; + } + else + { + return b; + } + } + + /* #impl-shared.NOT.1 */ + public static Bits NOT(Bits x) + { + return x.Not(); + } + + // #impl-shared.Ones.1 + public static Bits Ones(int N) + { + return Replicate(true, N); + } + + /* */ + public static Bits OR(Bits x, Bits y) + { + return x.Or(y); + } + + /* */ + public static decimal Real(BigInteger value) + { + return (decimal)value; + } + + // #impl-shared.ROR.2 + public static Bits ROR(Bits x, int shift) + { + /* assert shift >= 0; */ + + Bits result; + + if (shift == 0) + { + result = new Bits(x); + } + else + { + (result, _) = ROR_C(x, shift); + } + + return result; + } + + // #impl-shared.ROR_C.2 + public static (Bits, bool) ROR_C(Bits x, int shift) + { + int N = x.Count; + + /* assert shift != 0; */ + + int m = shift % N; + Bits result = OR(LSR(x, m), LSL(x, N - m)); + bool carry_out = result[N - 1]; + + return (result, carry_out); + } + + /* #impl-shared.Replicate.1 */ + public static Bits Replicate(int N, Bits x) + { + int M = x.Count; + + /* assert N MOD M == 0; */ + + return Replicate(x, N / M); + } + + /* #impl-shared.Replicate.2 */ + public static Bits Replicate(Bits x, int N) + { + int M = x.Count; + + bool[] dst = new bool[M * N]; + + for (int i = 0; i < N; i++) + { + x.CopyTo(dst, i * M); + } + + return new Bits(dst); + } + + /* #impl-shared.RoundDown.1 */ + public static BigInteger RoundDown(decimal x) + { + return (BigInteger)Decimal.Floor(x); + } + + // #impl-shared.RoundTowardsZero.1 + public static BigInteger RoundTowardsZero(decimal x) + { + if (x == 0.0m) + { + return (BigInteger)0m; + } + else if (x >= 0.0m) + { + return RoundDown(x); + } + else + { + return RoundUp(x); + } + } + + /* #impl-shared.RoundUp.1 */ + public static BigInteger RoundUp(decimal x) + { + return (BigInteger)Decimal.Ceiling(x); + } + + // #impl-shared.SInt.1 + public static BigInteger SInt(Bits x) + { + int N = x.Count; + + BigInteger result = 0; + + for (int i = 0; i <= N - 1; i++) + { + if (x[i]) + { + result = result + BigInteger.Pow(2, i); + } + } + + if (x[N - 1]) + { + result = result - BigInteger.Pow(2, N); + } + + return result; + } + + // #impl-shared.SignExtend.2 + public static Bits SignExtend(Bits x, int N) + { + int M = x.Count; + + /* assert N >= M; */ + + return Bits.Concat(Replicate(x[M - 1], N - M), x); + } + + /* #impl-shared.SignExtend.1 */ + public static Bits SignExtend(int N, Bits x) + { + return SignExtend(x, N); + } + + // #impl-shared.UInt.1 + public static BigInteger UInt(Bits x) + { + int N = x.Count; + + BigInteger result = 0; + + for (int i = 0; i <= N - 1; i++) + { + if (x[i]) + { + result = result + BigInteger.Pow(2, i); + } + } + + return result; + } + + // #impl-shared.ZeroExtend.2 + public static Bits ZeroExtend(Bits x, int N) + { + int M = x.Count; + + /* assert N >= M; */ + + return Bits.Concat(Zeros(N - M), x); + } + + /* #impl-shared.ZeroExtend.1 */ + public static Bits ZeroExtend(int N, Bits x) + { + return ZeroExtend(x, N); + } + + // #impl-shared.Zeros.1 + /* #impl-shared.Zeros.0 */ + public static Bits Zeros(int N) + { + return Replicate(false, N); + } +#endregion + +#region "functions/crc/" + // #impl-shared.BitReverse.1 + public static Bits BitReverse(Bits data) + { + int N = data.Count; + + Bits result = new Bits(N); + + for (int i = 0; i <= N - 1; i++) + { + result[N - i - 1] = data[i]; + } + + return result; + } + + // #impl-shared.Poly32Mod2.2 + public static Bits Poly32Mod2(Bits _data, Bits poly) + { + int N = _data.Count; + + /* assert N > 32; */ + + Bits data = new Bits(_data); + + for (int i = N - 1; i >= 32; i--) + { + if (data[i]) + { + data[i - 1, 0] = EOR(data[i - 1, 0], Bits.Concat(poly, Zeros(i - 32))); + } + } + + return data[31, 0]; + } +#endregion + +#region "functions/integer/" + /* #impl-shared.AddWithCarry.3 */ + public static (Bits, Bits) AddWithCarry(int N, Bits x, Bits y, bool carry_in) + { + BigInteger unsigned_sum = UInt(x) + UInt(y) + UInt(carry_in); + BigInteger signed_sum = SInt(x) + SInt(y) + UInt(carry_in); + + Bits result = unsigned_sum.SubBigInteger(N - 1, 0); // same value as signed_sum + + bool n = result[N - 1]; + bool z = IsZero(result); + bool c = !(UInt(result) == unsigned_sum); + bool v = !(SInt(result) == signed_sum); + + return (result, Bits.Concat(n, z, c, v)); + } +#endregion + +#region "functions/registers/" + public static readonly Bits[] _R; + + public static readonly Bits[] _V; + + public static Bits SP_EL0; + public static Bits SP_EL1; +#endregion + +#region "functions/system/" + // #impl-shared.ConditionHolds.1 + public static bool ConditionHolds(Bits cond) + { + bool result; + + // Evaluate base condition. + switch (cond[3, 1]) + { + case Bits bits when bits == "000": + result = (PSTATE.Z == true); // EQ or NE + break; + case Bits bits when bits == "001": + result = (PSTATE.C == true); // CS or CC + break; + case Bits bits when bits == "010": + result = (PSTATE.N == true); // MI or PL + break; + case Bits bits when bits == "011": + result = (PSTATE.V == true); // VS or VC + break; + case Bits bits when bits == "100": + result = (PSTATE.C == true && PSTATE.Z == false); // HI or LS + break; + case Bits bits when bits == "101": + result = (PSTATE.N == PSTATE.V); // GE or LT + break; + case Bits bits when bits == "110": + result = (PSTATE.N == PSTATE.V && PSTATE.Z == false); // GT or LE + break; + default: + case Bits bits when bits == "111": + result = true; // AL + break; + } + + // Condition flag values in the set '111x' indicate always true + // Otherwise, invert condition if necessary. + if (cond[0] == true && cond != "1111") + { + result = !result; + } + + return result; + } + + // #EL3 + public static readonly Bits EL3 = "11"; + // #EL2 + public static readonly Bits EL2 = "10"; + // #EL1 + public static readonly Bits EL1 = "01"; + // #EL0 + public static readonly Bits EL0 = "00"; + + /* #impl-shared.HaveEL.1 */ + public static bool HaveEL(Bits el) + { + if (el == EL1 || el == EL0) + { + return true; // EL1 and EL0 must exist + } + + return false; + } + + public static ProcState PSTATE; + + /* #ProcState */ + internal struct ProcState + { + public void NZCV(Bits nzcv) // ASL: ".<,,,>". + { + N = nzcv[3]; + Z = nzcv[2]; + C = nzcv[1]; + V = nzcv[0]; + } + + public void NZCV(bool n, bool z, bool c, bool v) // ASL: ".<,,,>". + { + N = n; + Z = z; + C = c; + V = v; + } + + public bool N; // Negative condition flag + public bool Z; // Zero condition flag + public bool C; // Carry condition flag + public bool V; // oVerflow condition flag + public Bits EL; // Exception Level + public bool SP; // Stack pointer select: 0=SP0, 1=SPx [AArch64 only] + } +#endregion + } +} diff --git a/Ryujinx.Tests/Cpu/Tester/Types/Bits.cs b/Ryujinx.Tests/Cpu/Tester/Types/Bits.cs new file mode 100644 index 000000000..30d632640 --- /dev/null +++ b/Ryujinx.Tests/Cpu/Tester/Types/Bits.cs @@ -0,0 +1,313 @@ +// https://github.com/LDj3SNuD/ARM_v8-A_AArch64_Instructions_Tester/blob/master/Tester/Types/Bits.cs + +// https://github.com/dotnet/corefx/blob/master/src/System.Collections/src/System/Collections/BitArray.cs + +using System; +using System.Collections; +using System.Numerics; + +namespace Ryujinx.Tests.Cpu.Tester.Types +{ + internal sealed class Bits : ICollection, IEnumerable, IEquatable + { + private BitArray bits; + + public Bits(bool[] values) => bits = new BitArray(values); + public Bits(byte[] bytes) => bits = new BitArray(bytes); + public Bits(Bits bits) => this.bits = new BitArray(bits.bits); + public Bits(int length) => bits = new BitArray(length); + public Bits(int length, bool defaultValue) => bits = new BitArray(length, defaultValue); + private Bits(BitArray bitArray) => bits = new BitArray(bitArray); + public Bits(ulong value) => bits = new BitArray(BitConverter.GetBytes(value)); + public Bits(uint value) => bits = new BitArray(BitConverter.GetBytes(value)); + public Bits(ushort value) => bits = new BitArray(BitConverter.GetBytes(value)); + public Bits(byte value) => bits = new BitArray(new byte[1] {value}); + + private BitArray ToBitArray() => new BitArray(bits); + public ulong ToUInt64() + { + byte[] dst = new byte[8]; + + bits.CopyTo(dst, 0); + + return BitConverter.ToUInt64(dst, 0); + } + public uint ToUInt32() + { + byte[] dst = new byte[4]; + + bits.CopyTo(dst, 0); + + return BitConverter.ToUInt32(dst, 0); + } + public ushort ToUInt16() + { + byte[] dst = new byte[2]; + + bits.CopyTo(dst, 0); + + return BitConverter.ToUInt16(dst, 0); + } + public byte ToByte() + { + byte[] dst = new byte[1]; + + bits.CopyTo(dst, 0); + + return dst[0]; + } + + public bool this[int index] // ASL: "<>". + { + get + { + return bits.Get(index); + } + set + { + bits.Set(index, value); + } + } + public Bits this[int highIndex, int lowIndex] // ASL: "<:>". + { + get + { + if (highIndex < lowIndex) + { + throw new IndexOutOfRangeException(); + } + + bool[] dst = new bool[highIndex - lowIndex + 1]; + + for (int i = lowIndex, n = 0; i <= highIndex; i++, n++) + { + dst[n] = bits.Get(i); + } + + return new Bits(dst); + } + set + { + if (highIndex < lowIndex) + { + throw new IndexOutOfRangeException(); + } + + for (int i = lowIndex, n = 0; i <= highIndex; i++, n++) + { + bits.Set(i, value.Get(n)); + } + } + } + + public bool IsReadOnly { get => false; } // Mutable. + public int Count { get => bits.Count; } + public bool IsSynchronized { get => bits.IsSynchronized; } + public object SyncRoot { get => bits.SyncRoot; } + public Bits And(Bits value) => new Bits(new BitArray(this.bits).And(value.bits)); // Immutable. + public void CopyTo(Array array, int index) => bits.CopyTo(array, index); + public bool Get(int index) => bits.Get(index); + public IEnumerator GetEnumerator() => bits.GetEnumerator(); + //public Bits LeftShift(int count) => new Bits(new BitArray(bits).LeftShift(count)); // Immutable. + public Bits Not() => new Bits(new BitArray(bits).Not()); // Immutable. + public Bits Or(Bits value) => new Bits(new BitArray(this.bits).Or(value.bits)); // Immutable. + //public Bits RightShift(int count) => new Bits(new BitArray(bits).RightShift(count)); // Immutable. + public void Set(int index, bool value) => bits.Set(index, value); + public void SetAll(bool value) => bits.SetAll(value); + public Bits Xor(Bits value) => new Bits(new BitArray(this.bits).Xor(value.bits)); // Immutable. + + public static Bits Concat(Bits highBits, Bits lowBits) // ASL: ":". + { + if (((object)lowBits == null) || ((object)highBits == null)) + { + throw new ArgumentNullException(); + } + + bool[] dst = new bool[lowBits.Count + highBits.Count]; + + lowBits.CopyTo(dst, 0); + highBits.CopyTo(dst, lowBits.Count); + + return new Bits(dst); + } + public static Bits Concat(bool bit3, bool bit2, bool bit1, bool bit0) // ASL: ":::". + { + return new Bits(new bool[] {bit0, bit1, bit2, bit3}); + } + + public static implicit operator Bits(bool value) => new Bits(1, value); + public static implicit operator Bits(string value) + { + if (String.IsNullOrEmpty(value)) + { + throw new InvalidCastException(); + } + + bool[] dst = new bool[value.Length]; + + for (int i = value.Length - 1, n = 0; i >= 0; i--, n++) + { + if (value[i] == '1') + { + dst[n] = true; + } + else if (value[i] == '0') + { + dst[n] = false; + } + else + { + throw new InvalidCastException(); + } + } + + return new Bits(dst); + } + public static explicit operator bool(Bits bit) + { + if (((object)bit == null) || (bit.Count != 1)) + { + throw new InvalidCastException(); + } + + return bit.Get(0); + } + + public static Bits operator +(Bits left, BigInteger right) // ASL: "+". + { + if (((object)left == null) || ((object)right == null)) + { + throw new ArgumentNullException(); + } + + BigInteger dst; + + switch (left.Count) + { + case 8: dst = left.ToByte() + right; break; + case 16: dst = left.ToUInt16() + right; break; + case 32: dst = left.ToUInt32() + right; break; + case 64: dst = left.ToUInt64() + right; break; + + default: throw new ArgumentOutOfRangeException(); + } + + return dst.SubBigInteger(left.Count - 1, 0); + } + public static Bits operator +(Bits left, Bits right) // ASL: "+". + { + if (((object)left == null) || ((object)right == null)) + { + throw new ArgumentNullException(); + } + + if (left.Count != right.Count) + { + throw new ArgumentException(); + } + + BigInteger dst; + + switch (left.Count) + { + case 8: dst = left.ToByte() + (BigInteger)right.ToByte(); break; + case 16: dst = left.ToUInt16() + (BigInteger)right.ToUInt16(); break; + case 32: dst = left.ToUInt32() + (BigInteger)right.ToUInt32(); break; + case 64: dst = left.ToUInt64() + (BigInteger)right.ToUInt64(); break; + + default: throw new ArgumentOutOfRangeException(); + } + + return dst.SubBigInteger(left.Count - 1, 0); + } + public static Bits operator -(Bits left, Bits right) // ASL: "-". + { + if (((object)left == null) || ((object)right == null)) + { + throw new ArgumentNullException(); + } + + if (left.Count != right.Count) + { + throw new ArgumentException(); + } + + BigInteger dst; + + switch (left.Count) + { + case 8: dst = left.ToByte() - (BigInteger)right.ToByte(); break; + case 16: dst = left.ToUInt16() - (BigInteger)right.ToUInt16(); break; + case 32: dst = left.ToUInt32() - (BigInteger)right.ToUInt32(); break; + case 64: dst = left.ToUInt64() - (BigInteger)right.ToUInt64(); break; + + default: throw new ArgumentOutOfRangeException(); + } + + return dst.SubBigInteger(left.Count - 1, 0); + } + public static bool operator ==(Bits left, Bits right) // ASL: "==". + { + if (((object)left == null) || ((object)right == null)) + { + throw new ArgumentNullException(); + } + + if (left.Count != right.Count) + { + return false; + } + + for (int i = 0; i <= left.Count - 1; i++) + { + if (left.Get(i) != right.Get(i)) + { + return false; + } + } + + return true; + } + public static bool operator !=(Bits left, Bits right) // ASL: "!=". + { + return !(left == right); + } + + public bool Equals(Bits right) // ASL: "==". + { + if ((object)right == null) + { + throw new ArgumentNullException(); + } + + Bits left = this; + + if (left.Count != right.Count) + { + return false; + } + + for (int i = 0; i <= left.Count - 1; i++) + { + if (left.Get(i) != right.Get(i)) + { + return false; + } + } + + return true; + } + public override bool Equals(object obj) + { + if (obj == null) + { + throw new ArgumentNullException(); + } + + Bits right = obj as Bits; + + return Equals(right); + } + public override int GetHashCode() => bits.GetHashCode(); + } +} diff --git a/Ryujinx.Tests/Cpu/Tester/Types/Integer.cs b/Ryujinx.Tests/Cpu/Tester/Types/Integer.cs new file mode 100644 index 000000000..c72f3e252 --- /dev/null +++ b/Ryujinx.Tests/Cpu/Tester/Types/Integer.cs @@ -0,0 +1,42 @@ +// https://github.com/LDj3SNuD/ARM_v8-A_AArch64_Instructions_Tester/blob/master/Tester/Types/Integer.cs + +using System; +using System.Numerics; + +namespace Ryujinx.Tests.Cpu.Tester.Types +{ + internal static class Integer + { + public static Bits SubBigInteger(this BigInteger x, int highIndex, int lowIndex) // ASL: "<:>". + { + if (highIndex < lowIndex) + { + throw new IndexOutOfRangeException(); + } + + Bits src = new Bits(x.ToByteArray()); + bool[] dst = new bool[highIndex - lowIndex + 1]; + + for (int i = lowIndex, n = 0; i <= highIndex; i++, n++) + { + if (i <= src.Count - 1) + { + dst[n] = src[i]; + } + else + { + dst[n] = (x.Sign != -1 ? false : true); // Zero / Sign Extension. + } + } + + return new Bits(dst); + } + + public static bool SubBigInteger(this BigInteger x, int index) // ASL: "<>". + { + Bits dst = x.SubBigInteger(index, index); + + return (bool)dst; + } + } +} diff --git a/Ryujinx.Tests/Ryujinx.Tests.csproj b/Ryujinx.Tests/Ryujinx.Tests.csproj index ae4ea6c33..77d86ec4f 100644 --- a/Ryujinx.Tests/Ryujinx.Tests.csproj +++ b/Ryujinx.Tests/Ryujinx.Tests.csproj @@ -1,15 +1,17 @@ netcoreapp2.0 + win10-x64 + Exe false false - - - + + + diff --git a/Ryujinx.sln b/Ryujinx.sln index 34a5f4887..036421f90 100644 --- a/Ryujinx.sln +++ b/Ryujinx.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.27130.2027 +VisualStudioVersion = 15.0.26730.8 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx", "Ryujinx\Ryujinx.csproj", "{074045D4-3ED2-4711-9169-E385F2BFB5A0}" EndProject @@ -9,9 +9,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Tests", "Ryujinx.Te EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Core", "Ryujinx.Core\Ryujinx.Core.csproj", "{CB92CFF9-1D62-4D4F-9E88-8130EF61E351}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChocolArm64", "ChocolArm64\ChocolArm64.csproj", "{2345A1A7-8DEF-419B-9AFB-4DFD41D20D05}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChocolArm64", "ChocolArm64\ChocolArm64.csproj", "{2345A1A7-8DEF-419B-9AFB-4DFD41D20D05}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Graphics", "Ryujinx.Graphics\Ryujinx.Graphics.csproj", "{EAAE36AF-7781-4578-A7E0-F0EFD2025569}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics", "Ryujinx.Graphics\Ryujinx.Graphics.csproj", "{EAAE36AF-7781-4578-A7E0-F0EFD2025569}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio", "Ryujinx.Audio\Ryujinx.Audio.csproj", "{5C1D818E-682A-46A5-9D54-30006E26C270}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -39,6 +41,10 @@ Global {EAAE36AF-7781-4578-A7E0-F0EFD2025569}.Debug|Any CPU.Build.0 = Debug|Any CPU {EAAE36AF-7781-4578-A7E0-F0EFD2025569}.Release|Any CPU.ActiveCfg = Release|Any CPU {EAAE36AF-7781-4578-A7E0-F0EFD2025569}.Release|Any CPU.Build.0 = Release|Any CPU + {5C1D818E-682A-46A5-9D54-30006E26C270}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5C1D818E-682A-46A5-9D54-30006E26C270}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5C1D818E-682A-46A5-9D54-30006E26C270}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5C1D818E-682A-46A5-9D54-30006E26C270}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Ryujinx/Ryujinx.conf b/Ryujinx/Ryujinx.conf index e8effac1d..0c88b34b4 100644 --- a/Ryujinx/Ryujinx.conf +++ b/Ryujinx/Ryujinx.conf @@ -1,25 +1,37 @@ -#Enabled print informations logs +#Enable cpu memory checks (slow) +Enable_Memory_Checks = false + +#Enable print informations logs Logging_Enable_Info = true -#Enabled print trace logs +#Enable print trace logs Logging_Enable_Trace = false -#Enabled print debug logs +#Enable print debug logs Logging_Enable_Debug = false -#Enabled print warning logs +#Enable print warning logs Logging_Enable_Warn = true -#Enabled print error logs +#Enable print error logs Logging_Enable_Error = true -#Enabled print fatal logs +#Enable print fatal logs Logging_Enable_Fatal = true -#Enabled print Ipc logs +#Enable print stubbed calls logs +Logging_Enable_Stub = false + +#Enable print Ipc logs Logging_Enable_Ipc = false -#Saved logs into Ryujinx.log +#Enable log filter +Logging_Enable_Filter = false + +#Filtered log classes, seperated by ',', eg. `Logging_Filtered_Classes = Loader,ServiceFS` +Logging_Filtered_Classes = + +#Save logs into Ryujinx.log Logging_Enable_LogFile = false #https://github.com/opentk/opentk/blob/develop/src/OpenTK/Input/Key.cs diff --git a/Ryujinx/Ryujinx.csproj b/Ryujinx/Ryujinx.csproj index bc5dbe042..f2d9cafe9 100644 --- a/Ryujinx/Ryujinx.csproj +++ b/Ryujinx/Ryujinx.csproj @@ -11,6 +11,7 @@ + diff --git a/Ryujinx/Ui/GLScreen.cs b/Ryujinx/Ui/GLScreen.cs index b0dca81b7..6b6ae6a01 100644 --- a/Ryujinx/Ui/GLScreen.cs +++ b/Ryujinx/Ui/GLScreen.cs @@ -1,6 +1,5 @@ using OpenTK; using OpenTK.Graphics; -using OpenTK.Graphics.OpenGL; using OpenTK.Input; using Ryujinx.Core; using Ryujinx.Core.Input; @@ -39,7 +38,7 @@ namespace Ryujinx { VSync = VSyncMode.On; - Renderer.InitializeFrameBuffer(); + Renderer.SetWindowSize(Width, Height); } protected override void OnUpdateFrame(FrameEventArgs e) @@ -156,6 +155,13 @@ namespace Ryujinx Ns.Hid.SetTouchPoints(); } + Ns.Hid.SetJoyconButton( + HidControllerId.CONTROLLER_HANDHELD, + HidControllerLayouts.Handheld_Joined, + CurrentButton, + LeftJoystick, + RightJoystick); + Ns.Hid.SetJoyconButton( HidControllerId.CONTROLLER_HANDHELD, HidControllerLayouts.Main, @@ -168,19 +174,17 @@ namespace Ryujinx { Ns.Statistics.StartSystemFrame(); - GL.Viewport(0, 0, Width, Height); - Title = $"Ryujinx Screen - (Vsync: {VSync} - FPS: {Ns.Statistics.SystemFrameRate:0} - Guest FPS: " + $"{Ns.Statistics.GameFrameRate:0})"; - GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); - Renderer.RunActions(); Renderer.Render(); SwapBuffers(); Ns.Statistics.EndSystemFrame(); + + Ns.Os.SignalVsync(); } protected override void OnResize(EventArgs e) diff --git a/Ryujinx/Ui/Program.cs b/Ryujinx/Ui/Program.cs index b67e52bdc..f9d40eb56 100644 --- a/Ryujinx/Ui/Program.cs +++ b/Ryujinx/Ui/Program.cs @@ -1,4 +1,6 @@ -using Ryujinx.Core; +using Ryujinx.Audio; +using Ryujinx.Audio.OpenAL; +using Ryujinx.Core; using Ryujinx.Graphics.Gal; using Ryujinx.Graphics.Gal.OpenGL; using System; @@ -10,15 +12,17 @@ namespace Ryujinx { static void Main(string[] args) { - AOptimizations.DisableMemoryChecks = true; - Config.Read(); + AOptimizations.DisableMemoryChecks = !Config.EnableMemoryChecks; + Console.Title = "Ryujinx Console"; IGalRenderer Renderer = new OpenGLRenderer(); - Switch Ns = new Switch(Renderer); + IAalOutput AudioOut = new OpenALAudioOut(); + + Switch Ns = new Switch(Renderer, AudioOut); if (args.Length == 1) { @@ -26,29 +30,34 @@ namespace Ryujinx { string[] RomFsFiles = Directory.GetFiles(args[0], "*.istorage"); + if (RomFsFiles.Length == 0) + { + RomFsFiles = Directory.GetFiles(args[0], "*.romfs"); + } + if (RomFsFiles.Length > 0) { - Logging.Info("Loading as cart with RomFS."); + Logging.Info(LogClass.Loader, "Loading as cart with RomFS."); Ns.LoadCart(args[0], RomFsFiles[0]); } else { - Logging.Info("Loading as cart WITHOUT RomFS."); + Logging.Info(LogClass.Loader, "Loading as cart WITHOUT RomFS."); Ns.LoadCart(args[0]); } } else if (File.Exists(args[0])) { - Logging.Info("Loading as homebrew."); + Logging.Info(LogClass.Loader, "Loading as homebrew."); Ns.LoadProgram(args[0]); } } else { - Logging.Error("Please specify the folder with the NSOs/IStorage or a NSO/NRO."); + Logging.Error(LogClass.Loader, "Please specify the folder with the NSOs/IStorage or a NSO/NRO."); } using (GLScreen Screen = new GLScreen(Ns, Renderer))