diff --git a/ARMeilleure/Decoders/OpCode32.cs b/ARMeilleure/Decoders/OpCode32.cs index 0d8ad1fdb..92487c6e4 100644 --- a/ARMeilleure/Decoders/OpCode32.cs +++ b/ARMeilleure/Decoders/OpCode32.cs @@ -13,11 +13,25 @@ namespace ARMeilleure.Decoders Cond = (Condition)((uint)opCode >> 28); } + public bool IsThumb() + { + return this is OpCodeT16 || this is OpCodeT32; + } + public uint GetPc() { // Due to backwards compatibility and legacy behavior of ARMv4 CPUs pipeline, // the PC actually points 2 instructions ahead. - return (uint)Address + (uint)OpCodeSizeInBytes * 2; + if (IsThumb()) + { + // PC is ahead by 4 in thumb mode whether or not the current instruction + // is 16 or 32 bit. + return (uint)Address + 4u; + } + else + { + return (uint)Address + 8u; + } } } } \ No newline at end of file diff --git a/ARMeilleure/Decoders/OpCodeT32BImm20.cs b/ARMeilleure/Decoders/OpCodeT32BImm20.cs new file mode 100644 index 000000000..8ed4b4b1f --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeT32BImm20.cs @@ -0,0 +1,29 @@ +using ARMeilleure.Instructions; + +namespace ARMeilleure.Decoders +{ + class OpCodeT32BImm20 : OpCodeT32, IOpCode32BImm + { + public long Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32BImm20(inst, address, opCode); + + public OpCodeT32BImm20(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + uint pc = GetPc(); + + int imm11 = (opCode >> 0) & 0x7ff; + int j2 = (opCode >> 11) & 1; + int j1 = (opCode >> 13) & 1; + int imm6 = (opCode >> 16) & 0x3f; + int s = (opCode >> 26) & 1; + + int imm32 = imm11 | (imm6 << 11) | (j1 << 17) | (j2 << 18) | (s << 19); + imm32 = (imm32 << 13) >> 12; + + Immediate = pc + imm32; + + Cond = (Condition)((opCode >> 22) & 0xf); + } + } +} \ No newline at end of file diff --git a/ARMeilleure/Decoders/OpCodeT32BImm24.cs b/ARMeilleure/Decoders/OpCodeT32BImm24.cs new file mode 100644 index 000000000..4381be477 --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeT32BImm24.cs @@ -0,0 +1,35 @@ +using ARMeilleure.Instructions; + +namespace ARMeilleure.Decoders +{ + class OpCodeT32BImm24 : OpCodeT32, IOpCode32BImm + { + public long Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32BImm24(inst, address, opCode); + + public OpCodeT32BImm24(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + uint pc = GetPc(); + + if (inst.Name == InstName.Blx) + { + pc &= ~3u; + } + + int imm11 = (opCode >> 0) & 0x7ff; + int j2 = (opCode >> 11) & 1; + int j1 = (opCode >> 13) & 1; + int imm10 = (opCode >> 16) & 0x3ff; + int s = (opCode >> 26) & 1; + + int i1 = j1 ^ s ^ 1; + int i2 = j2 ^ s ^ 1; + + int imm32 = imm11 | (imm10 << 11) | (i2 << 21) | (i1 << 22) | (s << 23); + imm32 = (imm32 << 9) >> 8; + + Immediate = pc + imm32; + } + } +} \ No newline at end of file diff --git a/ARMeilleure/Decoders/OpCodeTable.cs b/ARMeilleure/Decoders/OpCodeTable.cs index d290e5542..ad696104e 100644 --- a/ARMeilleure/Decoders/OpCodeTable.cs +++ b/ARMeilleure/Decoders/OpCodeTable.cs @@ -1050,7 +1050,11 @@ namespace ARMeilleure.Decoders SetT32("11101011010xxxxx0xxxxxxxxxxxxxxx", InstName.Adc, InstEmit32.Adc, OpCodeT32AluRsImm.Create); SetT32("11101011000<xxxx0xxx<<<<xxxxxxxx", InstName.Add, InstEmit32.Add, OpCodeT32AluRsImm.Create); SetT32("11101010000<xxxx0xxx<<<<xxxxxxxx", InstName.And, InstEmit32.And, OpCodeT32AluRsImm.Create); + SetT32("11110x<<<xxxxxxx10x0xxxxxxxxxxxx", InstName.B, InstEmit32.B, OpCodeT32BImm20.Create); + SetT32("11110xxxxxxxxxxx10x1xxxxxxxxxxxx", InstName.B, InstEmit32.B, OpCodeT32BImm24.Create); SetT32("11101010001xxxxx0xxxxxxxxxxxxxxx", InstName.Bic, InstEmit32.Bic, OpCodeT32AluRsImm.Create); + SetT32("11110xxxxxxxxxxx11x1xxxxxxxxxxxx", InstName.Bl, InstEmit32.Bl, OpCodeT32BImm24.Create); + SetT32("11110xxxxxxxxxxx11x0xxxxxxxxxxx0", InstName.Blx, InstEmit32.Blx, OpCodeT32BImm24.Create); SetT32("111010110001xxxx0xxx1111xxxxxxxx", InstName.Cmn, InstEmit32.Cmn, OpCodeT32AluRsImm.Create); SetT32("111010111011xxxx0xxx1111xxxxxxxx", InstName.Cmp, InstEmit32.Cmp, OpCodeT32AluRsImm.Create); SetT32("11101010100<xxxx0xxx<<<<xxxxxxxx", InstName.Eor, InstEmit32.Eor, OpCodeT32AluRsImm.Create); diff --git a/ARMeilleure/Instructions/InstEmitAluHelper.cs b/ARMeilleure/Instructions/InstEmitAluHelper.cs index 8bf53ed41..67d9e915e 100644 --- a/ARMeilleure/Instructions/InstEmitAluHelper.cs +++ b/ARMeilleure/Instructions/InstEmitAluHelper.cs @@ -128,7 +128,7 @@ namespace ARMeilleure.Instructions { Debug.Assert(value.Type == OperandType.I32); - if (IsThumb(context.CurrOp)) + if (((OpCode32)context.CurrOp).IsThumb()) { bool isReturn = IsA32Return(context); if (!isReturn) diff --git a/ARMeilleure/Instructions/InstEmitFlow32.cs b/ARMeilleure/Instructions/InstEmitFlow32.cs index 2b0525050..064aeb36d 100644 --- a/ARMeilleure/Instructions/InstEmitFlow32.cs +++ b/ARMeilleure/Instructions/InstEmitFlow32.cs @@ -34,7 +34,7 @@ namespace ARMeilleure.Instructions uint pc = op.GetPc(); - bool isThumb = IsThumb(context.CurrOp); + bool isThumb = ((OpCode32)context.CurrOp).IsThumb(); uint currentPc = isThumb ? pc | 1 @@ -61,7 +61,7 @@ namespace ARMeilleure.Instructions Operand addr = context.Copy(GetIntA32(context, op.Rm)); Operand bitOne = context.BitwiseAnd(addr, Const(1)); - bool isThumb = IsThumb(context.CurrOp); + bool isThumb = ((OpCode32)context.CurrOp).IsThumb(); uint currentPc = isThumb ? (pc - 2) | 1 diff --git a/ARMeilleure/Instructions/InstEmitHelper.cs b/ARMeilleure/Instructions/InstEmitHelper.cs index 773f6bd60..a22bb3fb7 100644 --- a/ARMeilleure/Instructions/InstEmitHelper.cs +++ b/ARMeilleure/Instructions/InstEmitHelper.cs @@ -10,11 +10,6 @@ namespace ARMeilleure.Instructions { static class InstEmitHelper { - public static bool IsThumb(OpCode op) - { - return op is OpCodeT16 || op is OpCodeT32; - } - public static Operand GetExtendedM(ArmEmitterContext context, int rm, IntType type) { Operand value = GetIntOrZR(context, rm); diff --git a/Ryujinx.Tests/Cpu/CpuTestT32Flow.cs b/Ryujinx.Tests/Cpu/CpuTestT32Flow.cs new file mode 100644 index 000000000..2c83b01d8 --- /dev/null +++ b/Ryujinx.Tests/Cpu/CpuTestT32Flow.cs @@ -0,0 +1,167 @@ +using ARMeilleure.State; +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + [Category("T32Flow")] + public sealed class CpuTestT32Flow : CpuTest32 + { + [Test] + public void TestT32B1() + { + // BNE label + ThumbOpcode(0xf040); + ThumbOpcode(0x8240); + for (int i = 0; i < 576; i++) + { + ThumbOpcode(0xe7fe); + } + // label: BX LR + ThumbOpcode(0x4770); + + GetContext().SetPstateFlag(PState.TFlag, true); + + ExecuteOpcodes(runUnicorn: false); + } + + [Test] + public void TestT32B2() + { + // BNE label1 + ThumbOpcode(0xf040); + ThumbOpcode(0x8242); + // label2: BNE label3 + ThumbOpcode(0xf040); + ThumbOpcode(0x8242); + for (int i = 0; i < 576; i++) + { + ThumbOpcode(0xe7fe); + } + // label1: BNE label2 + ThumbOpcode(0xf47f); + ThumbOpcode(0xadbc); + // label3: BX LR + ThumbOpcode(0x4770); + + GetContext().SetPstateFlag(PState.TFlag, true); + + ExecuteOpcodes(runUnicorn: false); + } + + [Test] + public void TestT32B3() + { + // B.W label + ThumbOpcode(0xf000); + ThumbOpcode(0xba40); + for (int i = 0; i < 576; i++) + { + ThumbOpcode(0xe7fe); + } + // label: BX LR + ThumbOpcode(0x4770); + + GetContext().SetPstateFlag(PState.TFlag, true); + + ExecuteOpcodes(runUnicorn: false); + } + + [Test] + public void TestT32B4() + { + // B.W label1 + ThumbOpcode(0xf000); + ThumbOpcode(0xba42); + // label2: B.W label3 + ThumbOpcode(0xf000); + ThumbOpcode(0xba42); + for (int i = 0; i < 576; i++) + { + ThumbOpcode(0xe7fe); + } + // label1: B.W label2 + ThumbOpcode(0xf7ff); + ThumbOpcode(0xbdbc); + // label3: BX LR + ThumbOpcode(0x4770); + + GetContext().SetPstateFlag(PState.TFlag, true); + + ExecuteOpcodes(runUnicorn: false); + } + + [Test] + public void TestT32Bl() + { + // BL label + ThumbOpcode(0xf000); + ThumbOpcode(0xf840); + for (int i = 0; i < 64; i++) + { + ThumbOpcode(0xe7fe); + } + ThumbOpcode(0x4670); // label: MOV R0, LR + ThumbOpcode(0x2100); // MOVS R1, #0 + ThumbOpcode(0x468e); // MOV LR, R1 + ThumbOpcode(0x4770); // BX LR + + GetContext().SetPstateFlag(PState.TFlag, true); + + ExecuteOpcodes(runUnicorn: false); + + Assert.That(GetContext().GetX(0), Is.EqualTo(0x1005)); + } + + [Test] + public void TestT32Blx1() + { + // BLX label + ThumbOpcode(0xf000); + ThumbOpcode(0xe840); + for (int i = 0; i < 64; i++) + { + ThumbOpcode(0x4770); + } + // .arm ; label: MOV R0, LR + Opcode(0xe1a0000e); + // MOV LR, #0 + Opcode(0xe3a0e000); + // BX LR + Opcode(0xe12fff1e); + + GetContext().SetPstateFlag(PState.TFlag, true); + + ExecuteOpcodes(runUnicorn: false); + + Assert.That(GetContext().GetX(0), Is.EqualTo(0x1005)); + Assert.That(GetContext().GetPstateFlag(PState.TFlag), Is.EqualTo(false)); + } + + [Test] + public void TestT32Blx2() + { + // NOP + ThumbOpcode(0xbf00); + // BLX label + ThumbOpcode(0xf000); + ThumbOpcode(0xe840); + for (int i = 0; i < 63; i++) + { + ThumbOpcode(0x4770); + } + // .arm ; label: MOV R0, LR + Opcode(0xe1a0000e); + // MOV LR, #0 + Opcode(0xe3a0e000); + // BX LR + Opcode(0xe12fff1e); + + GetContext().SetPstateFlag(PState.TFlag, true); + + ExecuteOpcodes(runUnicorn: false); + + Assert.That(GetContext().GetX(0), Is.EqualTo(0x1007)); + Assert.That(GetContext().GetPstateFlag(PState.TFlag), Is.EqualTo(false)); + } + } +} \ No newline at end of file