From 641e68db959631a4cc2b1b416529c94bf0f14b36 Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Fri, 22 Nov 2019 22:04:31 +1000 Subject: [PATCH] CPU/Recompiler: Implement b{gez,ltz}(al)? --- README.md | 3 +- src/core/cpu_recompiler_code_generator.cpp | 62 ++++++++++++------- src/core/cpu_recompiler_code_generator.h | 2 +- .../cpu_recompiler_code_generator_x64.cpp | 35 +++++++++-- src/core/cpu_recompiler_types.h | 8 ++- 5 files changed, 76 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 0d81978f3..a21dfc254 100644 --- a/README.md +++ b/README.md @@ -60,8 +60,9 @@ To access the menus with the controller, press the right stick down and use the - Passes amidog's CPU and GTE tests, partial passing of CPX tests ## Screenshots -![Spyro 2](https://raw.githubusercontent.com/stenzek/duckstation/md-images/spyro.jpg) +![Final Fantasy 7](https://raw.githubusercontent.com/stenzek/duckstation/md-images/ff7.jpg) ![Final Fantasy 8](https://raw.githubusercontent.com/stenzek/duckstation/md-images/ff8.jpg) +![Spyro 2](https://raw.githubusercontent.com/stenzek/duckstation/md-images/spyro.jpg) ## Disclaimers diff --git a/src/core/cpu_recompiler_code_generator.cpp b/src/core/cpu_recompiler_code_generator.cpp index ad8b394ec..12f22c0e0 100644 --- a/src/core/cpu_recompiler_code_generator.cpp +++ b/src/core/cpu_recompiler_code_generator.cpp @@ -93,6 +93,7 @@ bool CodeGenerator::CompileInstruction(const CodeBlockInstruction& cbi) case InstructionOp::j: case InstructionOp::jal: + case InstructionOp::b: case InstructionOp::beq: case InstructionOp::bne: case InstructionOp::bgtz: @@ -1029,7 +1030,7 @@ bool CodeGenerator::Compile_Branch(const CodeBlockInstruction& cbi) OrValues(AndValues(m_register_cache.ReadGuestRegister(Reg::pc, false), Value::FromConstantU32(0xF0000000)), Value::FromConstantU32(cbi.instruction.j.target << 2)); - EmitBranch(Condition::Always, (cbi.instruction.op == InstructionOp::jal) ? Reg::ra : Reg::count, + EmitBranch(Condition::Always, (cbi.instruction.op == InstructionOp::jal) ? Reg::ra : Reg::count, false, std::move(branch_target)); } break; @@ -1041,15 +1042,13 @@ bool CodeGenerator::Compile_Branch(const CodeBlockInstruction& cbi) // npc = rs, link to rt Value branch_target = m_register_cache.ReadGuestRegister(cbi.instruction.r.rs); EmitBranch(Condition::Always, - (cbi.instruction.r.funct == InstructionFunct::jalr) ? cbi.instruction.r.rd : Reg::count, + (cbi.instruction.r.funct == InstructionFunct::jalr) ? cbi.instruction.r.rd : Reg::count, false, std::move(branch_target)); } break; case InstructionOp::beq: case InstructionOp::bne: - case InstructionOp::bgtz: - case InstructionOp::blez: { // npc = pc + (sext(imm) << 2) Value branch_target = AddValues(m_register_cache.ReadGuestRegister(Reg::pc, false), @@ -1060,27 +1059,42 @@ bool CodeGenerator::Compile_Branch(const CodeBlockInstruction& cbi) Value rhs = m_register_cache.ReadGuestRegister(cbi.instruction.i.rt); EmitCmp(lhs.host_reg, rhs); - Condition condition; - switch (cbi.instruction.op) - { - case InstructionOp::beq: - condition = Condition::Equal; - break; - case InstructionOp::bne: - condition = Condition::NotEqual; - break; - case InstructionOp::bgtz: - condition = Condition::GreaterThanZero; - break; - case InstructionOp::blez: - condition = Condition::LessOrEqualToZero; - break; - default: - condition = Condition::Always; - break; - } + const Condition condition = (cbi.instruction.op == InstructionOp::beq) ? Condition::Equal : Condition::NotEqual; + EmitBranch(condition, Reg::count, false, std::move(branch_target)); + } + break; - EmitBranch(condition, Reg::count, std::move(branch_target)); + case InstructionOp::bgtz: + case InstructionOp::blez: + { + // npc = pc + (sext(imm) << 2) + Value branch_target = AddValues(m_register_cache.ReadGuestRegister(Reg::pc, false), + Value::FromConstantU32(cbi.instruction.i.imm_sext32() << 2)); + + // branch <- rs op 0 + Value lhs = m_register_cache.ReadGuestRegister(cbi.instruction.i.rs, true, true); + EmitCmp(lhs.host_reg, Value::FromConstantU32(0)); + + const Condition condition = + (cbi.instruction.op == InstructionOp::bgtz) ? Condition::Greater : Condition::LessOrEqual; + EmitBranch(condition, Reg::count, false, std::move(branch_target)); + } + break; + + case InstructionOp::b: + { + // npc = pc + (sext(imm) << 2) + Value branch_target = AddValues(m_register_cache.ReadGuestRegister(Reg::pc, false), + Value::FromConstantU32(cbi.instruction.i.imm_sext32() << 2)); + + const u8 rt = static_cast(cbi.instruction.i.rt.GetValue()); + const bool bgez = ConvertToBoolUnchecked(rt & u8(1)); + const Condition condition = bgez ? Condition::PositiveOrZero : Condition::Negative; + const bool link = (rt & u8(0x1E)) == u8(0x10); + + Value lhs = m_register_cache.ReadGuestRegister(cbi.instruction.i.rs, true, true); + EmitTest(lhs.host_reg, lhs); + EmitBranch(condition, link ? Reg::ra : Reg::count, link, std::move(branch_target)); } break; diff --git a/src/core/cpu_recompiler_code_generator.h b/src/core/cpu_recompiler_code_generator.h index ab9ac980b..3f0809e40 100644 --- a/src/core/cpu_recompiler_code_generator.h +++ b/src/core/cpu_recompiler_code_generator.h @@ -74,7 +74,7 @@ public: void EmitStoreGuestMemory(const Value& address, const Value& value); // Branching, generates two paths. - void EmitBranch(Condition condition, Reg lr_reg, Value&& branch_target); + void EmitBranch(Condition condition, Reg lr_reg, bool always_link, Value&& branch_target); u32 PrepareStackForCall(); void RestoreStackAfterCall(u32 adjust_size); diff --git a/src/core/cpu_recompiler_code_generator_x64.cpp b/src/core/cpu_recompiler_code_generator_x64.cpp index 9b1c09a02..1212a6a7c 100644 --- a/src/core/cpu_recompiler_code_generator_x64.cpp +++ b/src/core/cpu_recompiler_code_generator_x64.cpp @@ -1621,35 +1621,58 @@ static void EmitConditionalJump(Condition condition, bool invert, Xbyak::CodeGen invert ? emit->jno(label) : emit->jo(label); break; - case Condition::GreaterThanZero: + case Condition::Greater: invert ? emit->jng(label) : emit->jg(label); break; - case Condition::LessOrEqualToZero: + case Condition::GreaterEqual: + invert ? emit->jnge(label) : emit->jge(label); + break; + + case Condition::Less: + invert ? emit->jnl(label) : emit->jl(label); + break; + + case Condition::LessOrEqual: invert ? emit->jnle(label) : emit->jle(label); break; + case Condition::Negative: + invert ? emit->jns(label) : emit->js(label); + break; + + case Condition::PositiveOrZero: + invert ? emit->js(label) : emit->jns(label); + break; + default: UnreachableCode(); break; } } -void CodeGenerator::EmitBranch(Condition condition, Reg lr_reg, Value&& branch_target) +void CodeGenerator::EmitBranch(Condition condition, Reg lr_reg, bool always_link, Value&& branch_target) { - Xbyak::Label skip_branch; - // we have to always read the old PC.. when we can push/pop the register cache state this won't be needed Value old_npc; if (lr_reg != Reg::count) + { old_npc = m_register_cache.ReadGuestRegister(Reg::npc, false, true); + if (always_link) + { + // can't cache because we have two branches + m_register_cache.WriteGuestRegister(lr_reg, std::move(old_npc)); + m_register_cache.FlushGuestRegister(lr_reg, true, true); + } + } // condition is inverted because we want the case for skipping it + Xbyak::Label skip_branch; if (condition != Condition::Always) EmitConditionalJump(condition, true, m_emit, skip_branch); // save the old PC if we want to - if (lr_reg != Reg::count) + if (lr_reg != Reg::count && !always_link) { // can't cache because we have two branches m_register_cache.WriteGuestRegister(lr_reg, std::move(old_npc)); diff --git a/src/core/cpu_recompiler_types.h b/src/core/cpu_recompiler_types.h index b48edc46b..bdb6680b6 100644 --- a/src/core/cpu_recompiler_types.h +++ b/src/core/cpu_recompiler_types.h @@ -30,8 +30,12 @@ enum class Condition: u8 NotEqual, Equal, Overflow, - GreaterThanZero, - LessOrEqualToZero, + Greater, + GreaterEqual, + LessOrEqual, + Less, + Negative, + PositiveOrZero, NotZero = NotEqual, Zero = Equal