diff --git a/src/frontend/A32/decoder/thumb16.inc b/src/frontend/A32/decoder/thumb16.inc index 92a7d0f6..f01c98aa 100644 --- a/src/frontend/A32/decoder/thumb16.inc +++ b/src/frontend/A32/decoder/thumb16.inc @@ -60,12 +60,15 @@ INST(thumb16_ADD_sp_t2, "ADD (SP plus imm, T2)", "101100000vvvvvvv") // INST(thumb16_SUB_sp, "SUB (SP minus imm)", "101100001vvvvvvv") // v4T // Hint instructions -INST(thumb16_NOP, "NOP", "1011111100000000") // v6T2 INST(thumb16_SEV, "SEV", "1011111101000000") // v7 INST(thumb16_SEVL, "SEVL", "1011111101010000") // v8 INST(thumb16_WFE, "WFE", "1011111100100000") // v7 INST(thumb16_WFI, "WFI", "1011111100110000") // v7 INST(thumb16_YIELD, "YIELD", "1011111100010000") // v7 +INST(thumb16_NOP, "NOP", "10111111----0000") // v7 + +// IT instruction +INST(thumb16_IT, "IT", "10111111iiiiiiii") // v7 // Miscellaneous 16-bit instructions INST(thumb16_SXTH, "SXTH", "1011001000mmmddd") // v6 diff --git a/src/frontend/A32/disassembler/disassembler_thumb.cpp b/src/frontend/A32/disassembler/disassembler_thumb.cpp index f908bfb9..0c85e7e4 100644 --- a/src/frontend/A32/disassembler/disassembler_thumb.cpp +++ b/src/frontend/A32/disassembler/disassembler_thumb.cpp @@ -269,6 +269,25 @@ public: return "yield"; } + std::string thumb16_IT(Imm<8> imm8) { + const Cond firstcond = imm8.Bits<4, 7, Cond>(); + const bool firstcond0 = imm8.Bit<4>(); + const auto [x, y, z] = [&]{ + if (imm8.Bits<0, 3>() == 0b1000) { + return std::make_tuple("", "", ""); + } + if (imm8.Bits<0, 2>() == 0b100) { + return std::make_tuple(imm8.Bit<3>() == firstcond0 ? "t" : "e", "", ""); + } + if (imm8.Bits<0, 1>() == 0b10) { + return std::make_tuple(imm8.Bit<3>() == firstcond0 ? "t" : "e", imm8.Bit<2>() == firstcond0 ? "t" : "e", ""); + } + // Sanity note: Here imm8.Bit<0>() is guaranteed to be == 1. (imm8 can never be 0bxxxx0000) + return std::make_tuple(imm8.Bit<3>() == firstcond0 ? "t" : "e", imm8.Bit<2>() == firstcond0 ? "t" : "e", imm8.Bit<1>() == firstcond0 ? "t" : "e"); + }(); + return fmt::format("it{}{}{} {}", x, y, z, firstcond); + } + std::string thumb16_SXTH(Reg m, Reg d) { return fmt::format("sxth {}, {}", d, m); } diff --git a/src/frontend/A32/translate/impl/thumb16.cpp b/src/frontend/A32/translate/impl/thumb16.cpp index 2ded96c1..481e01f6 100644 --- a/src/frontend/A32/translate/impl/thumb16.cpp +++ b/src/frontend/A32/translate/impl/thumb16.cpp @@ -697,11 +697,6 @@ bool ThumbTranslatorVisitor::thumb16_SUB_sp(Imm<7> imm7) { return true; } -// NOP -bool ThumbTranslatorVisitor::thumb16_NOP() { - return true; -} - // SEV bool ThumbTranslatorVisitor::thumb16_SEV() { if (!options.hook_hint_instructions) { @@ -742,6 +737,26 @@ bool ThumbTranslatorVisitor::thumb16_YIELD() { return RaiseException(Exception::Yield); } +// NOP +bool ThumbTranslatorVisitor::thumb16_NOP() { + return true; +} + +// IT{{{}}} +bool ThumbTranslatorVisitor::thumb16_IT(Imm<8> imm8) { + ASSERT_MSG((imm8.Bits<0, 3>() != 0b0000), "Decode Error"); + if (imm8.Bits<4, 7>() == 0b1111 || (imm8.Bits<4, 7>() == 0b1110 && Common::BitCount(imm8.Bits<0, 3>()) != 1)) { + return UnpredictableInstruction(); + } + if (ir.current_location.IT().IsInITBlock()) { + return UnpredictableInstruction(); + } + + const auto next_location = ir.current_location.AdvancePC(2).SetIT(ITState{imm8.ZeroExtend()}); + ir.SetTerm(IR::Term::LinkBlockFast{next_location}); + return false; +} + // SXTH , // Rd cannot encode R15. bool ThumbTranslatorVisitor::thumb16_SXTH(Reg m, Reg d) { diff --git a/src/frontend/A32/translate/impl/translate_thumb.h b/src/frontend/A32/translate/impl/translate_thumb.h index 0e61f58c..49f4675e 100644 --- a/src/frontend/A32/translate/impl/translate_thumb.h +++ b/src/frontend/A32/translate/impl/translate_thumb.h @@ -9,6 +9,7 @@ #include "frontend/imm.h" #include "frontend/A32/ir_emitter.h" #include "frontend/A32/location_descriptor.h" +#include "frontend/A32/translate/conditional_state.h" #include "frontend/A32/translate/translate.h" #include "frontend/A32/types.h" @@ -24,8 +25,11 @@ struct ThumbTranslatorVisitor final { } A32::IREmitter ir; + ConditionalState cond_state = ConditionalState::None; TranslationOptions options; + bool ConditionPassed(bool is_thumb_16); + bool InterpretThisInstruction(); bool UnpredictableInstruction(); bool UndefinedInstruction(); @@ -83,12 +87,13 @@ struct ThumbTranslatorVisitor final { bool thumb16_ADD_sp_t1(Reg d, Imm<8> imm8); bool thumb16_ADD_sp_t2(Imm<7> imm7); bool thumb16_SUB_sp(Imm<7> imm7); - bool thumb16_NOP(); bool thumb16_SEV(); bool thumb16_SEVL(); bool thumb16_WFE(); bool thumb16_WFI(); bool thumb16_YIELD(); + bool thumb16_NOP(); + bool thumb16_IT(Imm<8> imm8); bool thumb16_SXTH(Reg m, Reg d); bool thumb16_SXTB(Reg m, Reg d); bool thumb16_UXTH(Reg m, Reg d); diff --git a/src/frontend/A32/translate/translate_thumb.cpp b/src/frontend/A32/translate/translate_thumb.cpp index 35f7de5c..7077c52c 100644 --- a/src/frontend/A32/translate/translate_thumb.cpp +++ b/src/frontend/A32/translate/translate_thumb.cpp @@ -9,13 +9,14 @@ #include "common/assert.h" #include "common/bit_util.h" -#include "frontend/imm.h" #include "frontend/A32/decoder/thumb16.h" #include "frontend/A32/decoder/thumb32.h" #include "frontend/A32/ir_emitter.h" #include "frontend/A32/location_descriptor.h" +#include "frontend/A32/translate/conditional_state.h" #include "frontend/A32/translate/impl/translate_thumb.h" #include "frontend/A32/translate/translate.h" +#include "frontend/imm.h" namespace Dynarmic::A32 { namespace { @@ -28,6 +29,16 @@ bool IsThumb16(u16 first_part) { return (first_part & 0xF800) < 0xE800; } +bool IsUnconditionalInstruction(bool is_thumb_16, u32 instruction) { + if (!is_thumb_16) + return false; + if ((instruction & 0xFF00) == 0b10111110'00000000) // BKPT + return true; + if ((instruction & 0xFFC0) == 0b10111010'10000000) // HLT + return true; + return false; +} + std::tuple ReadThumbInstruction(u32 arm_pc, MemoryReadCodeFuncType memory_read_code) { u32 first_part = memory_read_code(arm_pc & 0xFFFFFFFC); if ((arm_pc & 0x2) != 0) { @@ -64,30 +75,44 @@ IR::Block TranslateThumb(LocationDescriptor descriptor, MemoryReadCodeFuncType m do { const u32 arm_pc = visitor.ir.current_location.PC(); const auto [thumb_instruction, inst_size] = ReadThumbInstruction(arm_pc, memory_read_code); + const bool is_thumb_16 = inst_size == ThumbInstSize::Thumb16; - if (inst_size == ThumbInstSize::Thumb16) { - if (const auto decoder = DecodeThumb16(static_cast(thumb_instruction))) { - should_continue = decoder->get().call(visitor, static_cast(thumb_instruction)); + if (IsUnconditionalInstruction(is_thumb_16, thumb_instruction) || visitor.ConditionPassed(is_thumb_16)) { + if (is_thumb_16) { + if (const auto decoder = DecodeThumb16(static_cast(thumb_instruction))) { + should_continue = decoder->get().call(visitor, static_cast(thumb_instruction)); + } else { + should_continue = visitor.thumb16_UDF(); + } } else { - should_continue = visitor.thumb16_UDF(); - } - } else { - if (const auto decoder = DecodeThumb32(thumb_instruction)) { - should_continue = decoder->get().call(visitor, thumb_instruction); - } else { - should_continue = visitor.thumb32_UDF(); + if (const auto decoder = DecodeThumb32(thumb_instruction)) { + should_continue = decoder->get().call(visitor, thumb_instruction); + } else { + should_continue = visitor.thumb32_UDF(); + } } } - const s32 advance_pc = (inst_size == ThumbInstSize::Thumb16) ? 2 : 4; - visitor.ir.current_location = visitor.ir.current_location.AdvancePC(advance_pc); - block.CycleCount()++; - } while (should_continue && !single_step); + if (visitor.cond_state == ConditionalState::Break) { + break; + } - if (single_step && should_continue) { - visitor.ir.SetTerm(IR::Term::LinkBlock{visitor.ir.current_location}); + visitor.ir.current_location = visitor.ir.current_location.AdvancePC(is_thumb_16 ? 2 : 4).AdvanceIT(); + block.CycleCount()++; + } while (should_continue && CondCanContinue(visitor.cond_state, visitor.ir) && !single_step); + + if (visitor.cond_state == ConditionalState::Translating || visitor.cond_state == ConditionalState::Trailing || single_step) { + if (should_continue) { + if (single_step) { + visitor.ir.SetTerm(IR::Term::LinkBlock{visitor.ir.current_location}); + } else { + visitor.ir.SetTerm(IR::Term::LinkBlockFast{visitor.ir.current_location}); + } + } } + ASSERT_MSG(block.HasTerminal(), "Terminal has not been set"); + block.SetEndLocation(visitor.ir.current_location); return block; @@ -122,6 +147,11 @@ bool TranslateSingleThumbInstruction(IR::Block& block, LocationDescriptor descri return should_continue; } +bool ThumbTranslatorVisitor::ConditionPassed(bool is_thumb_16) { + const Cond cond = ir.current_location.IT().Cond(); + return IsConditionPassed(cond, cond_state, ir, is_thumb_16 ? 2 : 4); +} + bool ThumbTranslatorVisitor::InterpretThisInstruction() { ir.SetTerm(IR::Term::Interpret(ir.current_location)); return false; diff --git a/tests/A32/fuzz_arm.cpp b/tests/A32/fuzz_arm.cpp index 2c69f754..b0dcf94d 100644 --- a/tests/A32/fuzz_arm.cpp +++ b/tests/A32/fuzz_arm.cpp @@ -18,6 +18,7 @@ #include "common/fp/fpsr.h" #include "common/llvm_disassemble.h" #include "common/scope_exit.h" +#include "frontend/A32/ITState.h" #include "frontend/A32/location_descriptor.h" #include "frontend/A32/translate/translate.h" #include "frontend/A32/types.h" @@ -36,8 +37,8 @@ namespace { using namespace Dynarmic; -bool ShouldTestInst(u32 instruction, u32 pc, bool is_thumb, bool is_last_inst) { - const A32::LocationDescriptor location = A32::LocationDescriptor{pc, {}, {}}.SetTFlag(is_thumb); +bool ShouldTestInst(u32 instruction, u32 pc, bool is_thumb, bool is_last_inst, A32::ITState it_state = {}) { + const A32::LocationDescriptor location = A32::LocationDescriptor{pc, {}, {}}.SetTFlag(is_thumb).SetIT(it_state); IR::Block block{location}; const bool should_continue = A32::TranslateSingleInstruction(block, location, instruction); @@ -145,7 +146,7 @@ u32 GenRandomArmInst(u32 pc, bool is_last_inst) { } } -std::vector GenRandomThumbInst(u32 pc, bool is_last_inst) { +std::vector GenRandomThumbInst(u32 pc, bool is_last_inst, A32::ITState it_state = {}) { static const struct InstructionGeneratorInfo { std::vector generators; std::vector invalid; @@ -162,8 +163,9 @@ std::vector GenRandomThumbInst(u32 pc, bool is_last_inst) { // List of instructions not to test static constexpr std::array do_not_test { - "thumb16_SETEND", "thumb16_BKPT", + "thumb16_IT", + "thumb16_SETEND", }; for (const auto& [fn, bitstring] : list) { @@ -181,7 +183,7 @@ std::vector GenRandomThumbInst(u32 pc, bool is_last_inst) { const u32 inst = instructions.generators[index].Generate(); const bool is_four_bytes = (inst >> 16) != 0; - if (ShouldTestInst(is_four_bytes ? Common::SwapHalves32(inst) : inst, pc, true, is_last_inst)) { + if (ShouldTestInst(is_four_bytes ? Common::SwapHalves32(inst) : inst, pc, true, is_last_inst, it_state)) { if (is_four_bytes) return { static_cast(inst >> 16), static_cast(inst) }; return { static_cast(inst) }; @@ -480,3 +482,55 @@ TEST_CASE("A32: Small random thumb block", "[thumb]") { RunTestInstance(jit, uni, jit_env, uni_env, regs, ext_reg, instructions, cpsr, fpcr, 5); } } + +TEST_CASE("A32: Test thumb IT instruction", "[thumb]") { + ThumbTestEnv jit_env{}; + ThumbTestEnv uni_env{}; + + Dynarmic::A32::Jit jit{GetUserConfig(jit_env)}; + A32Unicorn uni{uni_env}; + + A32Unicorn::RegisterArray regs; + A32Unicorn::ExtRegArray ext_reg; + std::vector instructions; + + for (size_t iteration = 0; iteration < 100000; ++iteration) { + std::generate(regs.begin(), regs.end(), [] { return RandInt(0, ~u32(0)); }); + std::generate(ext_reg.begin(), ext_reg.end(), [] { return RandInt(0, ~u32(0)); }); + + const size_t pre_instructions = RandInt(0, 3); + const size_t post_instructions = RandInt(5, 8); + + instructions.clear(); + + for (size_t i = 0; i < pre_instructions; i++) { + const std::vector inst = GenRandomThumbInst(instructions.size() * 2, false); + instructions.insert(instructions.end(), inst.begin(), inst.end()); + } + + // Emit IT instruction + A32::ITState it_state = [&]{ + while (true) { + const u16 imm8 = RandInt(0, 0xFF); + if (Common::Bits<0, 3>(imm8) == 0b0000 || Common::Bits<4, 7>(imm8) == 0b1111 || (Common::Bits<4, 7>(imm8) == 0b1110 && Common::BitCount(Common::Bits<0, 3>(imm8)) != 1)) { + continue; + } + instructions.push_back(0b1011111100000000 | imm8); + return A32::ITState{static_cast(imm8)}; + } + }(); + + for (size_t i = 0; i < post_instructions; i++) { + const std::vector inst = GenRandomThumbInst(instructions.size() * 2, i == post_instructions - 1, it_state); + instructions.insert(instructions.end(), inst.begin(), inst.end()); + it_state = it_state.Advance(); + } + + const u32 start_address = 100; + const u32 cpsr = (RandInt(0, 0xF) << 28) | 0x1F0; + const u32 fpcr = RandomFpcr(); + + regs[15] = start_address; + RunTestInstance(jit, uni, jit_env, uni_env, regs, ext_reg, instructions, cpsr, fpcr, pre_instructions + 1 + post_instructions); + } +}