From 934c846d12c2cf2b1921eeb9cd5ec9e7b8473a95 Mon Sep 17 00:00:00 2001 From: ApfelTeeSaft <91074565+ApfelTeeSaft@users.noreply.github.com> Date: Tue, 7 Jul 2026 16:33:36 +0200 Subject: [PATCH] core/arm: model ARM11 MPCore timing per TRM This Commit also reverts my initial "reimplementation / revertion" of the legacy "1 tick : 1 instruction" cpu cycling. - Fix a bug related to the Get helper. It performed a parallel bit deposit where an extract was required - Branch prediction stuff - Charge now loads/stores their 1-cycle issue cost instead of flat interlock penalty Add VFP11 costs (was VFP -> decay to 1 cycle generic entry) - Implement Thumb dispatch --- CMakeModules/GenerateSettingKeys.cmake | 1 - src/citra_qt/configuration/config.cpp | 2 - .../configuration/configure_debug.cpp | 11 - src/citra_qt/configuration/configure_debug.h | 5 - src/citra_qt/configuration/configure_debug.ui | 80 +++--- src/common/settings.cpp | 2 - src/common/settings.h | 2 - src/core/arm/dynarmic/arm_dynarmic.cpp | 4 - src/core/arm/dynarmic/arm_tick_counts.cpp | 255 ++++++++++++------ 9 files changed, 209 insertions(+), 153 deletions(-) diff --git a/CMakeModules/GenerateSettingKeys.cmake b/CMakeModules/GenerateSettingKeys.cmake index 986682059..b7be40f51 100644 --- a/CMakeModules/GenerateSettingKeys.cmake +++ b/CMakeModules/GenerateSettingKeys.cmake @@ -9,7 +9,6 @@ foreach(KEY IN ITEMS "use_artic_base_controller" "enable_gamemode" "use_cpu_jit" - "cpu_legacy_instruction_ticks" "cpu_clock_percentage" "is_new_3ds" "lle_applets" diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index eca7d4a28..61045e12a 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -474,7 +474,6 @@ void QtConfig::ReadCoreValues() { qt_config->beginGroup(QStringLiteral("Core")); ReadGlobalSetting(Settings::values.cpu_clock_percentage); - ReadGlobalSetting(Settings::values.cpu_legacy_instruction_ticks); if (global) { ReadBasicSetting(Settings::values.use_cpu_jit); @@ -1072,7 +1071,6 @@ void QtConfig::SaveCoreValues() { qt_config->beginGroup(QStringLiteral("Core")); WriteGlobalSetting(Settings::values.cpu_clock_percentage); - WriteGlobalSetting(Settings::values.cpu_legacy_instruction_ticks); if (global) { WriteBasicSetting(Settings::values.use_cpu_jit); diff --git a/src/citra_qt/configuration/configure_debug.cpp b/src/citra_qt/configuration/configure_debug.cpp index 1162837ca..73bc2483d 100644 --- a/src/citra_qt/configuration/configure_debug.cpp +++ b/src/citra_qt/configuration/configure_debug.cpp @@ -143,8 +143,6 @@ void ConfigureDebug::SetConfiguration() { ui->toggle_renderer_debug->setChecked(Settings::values.renderer_debug.GetValue()); ui->toggle_pica_debugging->setChecked(Settings::values.pica_debugging.GetValue()); ui->toggle_dump_command_buffers->setChecked(Settings::values.dump_command_buffers.GetValue()); - ui->toggle_cpu_legacy_instruction_ticks->setChecked( - Settings::values.cpu_legacy_instruction_ticks.GetValue()); if (!Settings::IsConfiguringGlobal()) { if (Settings::values.cpu_clock_percentage.UsingGlobal()) { @@ -194,9 +192,6 @@ void ConfigureDebug::ApplyConfiguration() { Settings::values.dump_command_buffers = ui->toggle_dump_command_buffers->isChecked(); Settings::values.instant_debug_log = ui->instant_debug_log->isChecked(); - ConfigurationShared::ApplyPerGameSetting(&Settings::values.cpu_legacy_instruction_ticks, - ui->toggle_cpu_legacy_instruction_ticks, - cpu_legacy_instruction_ticks); ConfigurationShared::ApplyPerGameSetting( &Settings::values.cpu_clock_percentage, ui->clock_speed_combo, [this](s32) { return SliderToSettings(ui->slider_clock_speed->value()); }); @@ -206,15 +201,9 @@ void ConfigureDebug::SetupPerGameUI() { // Block the global settings if a game is currently running that overrides them if (Settings::IsConfiguringGlobal()) { ui->slider_clock_speed->setEnabled(Settings::values.cpu_clock_percentage.UsingGlobal()); - ui->toggle_cpu_legacy_instruction_ticks->setEnabled( - Settings::values.cpu_legacy_instruction_ticks.UsingGlobal()); return; } - ConfigurationShared::SetColoredTristate(ui->toggle_cpu_legacy_instruction_ticks, - Settings::values.cpu_legacy_instruction_ticks, - cpu_legacy_instruction_ticks); - connect(ui->clock_speed_combo, qOverload(&QComboBox::activated), this, [this](int index) { ui->slider_clock_speed->setEnabled(index == 1); ConfigurationShared::SetHighlight(ui->clock_speed_widget, index == 1); diff --git a/src/citra_qt/configuration/configure_debug.h b/src/citra_qt/configuration/configure_debug.h index adc2b5cea..8c77ec2d0 100644 --- a/src/citra_qt/configuration/configure_debug.h +++ b/src/citra_qt/configuration/configure_debug.h @@ -7,10 +7,6 @@ #include #include -namespace ConfigurationShared { - enum class CheckState; -} - namespace Ui { class ConfigureDebug; } @@ -30,5 +26,4 @@ public: private: std::unique_ptr ui; bool is_powered_on; - ConfigurationShared::CheckState cpu_legacy_instruction_ticks; }; diff --git a/src/citra_qt/configuration/configure_debug.ui b/src/citra_qt/configuration/configure_debug.ui index 80a75332d..e5fac44b1 100644 --- a/src/citra_qt/configuration/configure_debug.ui +++ b/src/citra_qt/configuration/configure_debug.ui @@ -61,11 +61,11 @@ - - - Pause next non-sysmodule process at start - - + + + Pause next non-sysmodule process at start + + @@ -125,14 +125,14 @@ - - - Flush log output on every message - - - <html><body>Immediately commits the debug log to file. Use this if Azahar crashes and the log output is being cut.<br>Enabling this feature will decrease performance, only use it for debugging purposes.</body></html> - - + + + Flush log output on every message + + + <html><body>Immediately commits the debug log to file. Use this if Azahar crashes and the log output is being cut.<br>Enabling this feature will decrease performance, only use it for debugging purposes.</body></html> + + @@ -226,13 +226,13 @@ - <html><head/><body>Underclocking can increase performance but may cause the application to freeze.<br/>Overclocking may reduce lag in applications but also might cause freezes</p></body></html> - - - Qt::RichText - - - + <html><head/><body>Underclocking can increase performance but may cause the application to freeze.<br/>Overclocking may reduce lag in applications but also might cause freezes</p></body></html> + + + Qt::RichText + + + @@ -243,16 +243,6 @@ - - - - <html><head/><body><p>Counts every ARM instruction as a single cycle instead of using the per-instruction timing table. A few applications contain scripted logic that is sensitive to the emulated CPU speed and only progresses correctly with this enabled (e.g. the rooftop chase in LEGO City Undercover: The Chase Begins). Leave unchecked unless an application misbehaves. Requires restarting the application to fully apply.</p></body></html> - - - Use legacy CPU cycle timing (1 tick per instruction) - - - @@ -260,13 +250,13 @@ - - - - Enable PICA200 debugging - - - + + + + Enable PICA200 debugging + + + @@ -314,14 +304,14 @@ - - - Enable RPC server - - - <html><head/><body><p>Enables the RPC server on port 45987. This allows remotely reading/writing guest memory, do not enable if you don't know what you are doing.</p></body></html> - - + + + Enable RPC server + + + <html><head/><body><p>Enables the RPC server on port 45987. This allows remotely reading/writing guest memory, do not enable if you don't know what you are doing.</p></body></html> + + diff --git a/src/common/settings.cpp b/src/common/settings.cpp index 209a35407..f2327ea5a 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -88,8 +88,6 @@ void LogSettings() { LOG_INFO(Config, "Azahar Configuration:"); log_setting("Core_UseCpuJit", values.use_cpu_jit.GetValue()); log_setting("Core_CPUClockPercentage", values.cpu_clock_percentage.GetValue()); - log_setting("Core_CpuLegacyInstructionTicks", - values.cpu_legacy_instruction_ticks.GetValue()); log_setting("Controller_UseArticController", values.use_artic_base_controller.GetValue()); log_setting("Renderer_UseGLES", values.use_gles.GetValue()); log_setting("Renderer_GraphicsAPI", GetGraphicsAPIName(values.graphics_api.GetValue())); diff --git a/src/common/settings.h b/src/common/settings.h index 37ce5fb17..e481d761f 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -482,8 +482,6 @@ struct Values { // Core Setting use_cpu_jit{true, Keys::use_cpu_jit}; SwitchableSetting cpu_clock_percentage{100, 5, 400, Keys::cpu_clock_percentage}; - SwitchableSetting cpu_legacy_instruction_ticks{false, - Keys::cpu_legacy_instruction_ticks}; SwitchableSetting is_new_3ds{true, Keys::is_new_3ds}; SwitchableSetting lle_applets{true, Keys::lle_applets}; SwitchableSetting deterministic_async_operations{false, diff --git a/src/core/arm/dynarmic/arm_dynarmic.cpp b/src/core/arm/dynarmic/arm_dynarmic.cpp index da3fddf13..64b740569 100644 --- a/src/core/arm/dynarmic/arm_dynarmic.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic.cpp @@ -8,7 +8,6 @@ #include #include "common/assert.h" #include "common/microprofile.h" -#include "common/settings.h" #include "core/arm/dynarmic/arm_dynarmic.h" #include "core/arm/dynarmic/arm_dynarmic_cp15.h" #include "core/arm/dynarmic/arm_exclusive_monitor.h" @@ -160,9 +159,6 @@ public: return static_cast(ticks <= 0 ? 0 : ticks); } std::uint64_t GetTicksForCode(bool is_thumb, VAddr, std::uint32_t instruction) override { - if (Settings::values.cpu_legacy_instruction_ticks.GetValue()) [[unlikely]] { - return 1; - } return Core::TicksForInstruction(is_thumb, instruction); } diff --git a/src/core/arm/dynarmic/arm_tick_counts.cpp b/src/core/arm/dynarmic/arm_tick_counts.cpp index 87f3ede25..33b167689 100644 --- a/src/core/arm/dynarmic/arm_tick_counts.cpp +++ b/src/core/arm/dynarmic/arm_tick_counts.cpp @@ -24,13 +24,13 @@ constexpr u32 GetMatchingBitsFromStringLiteral() { } template -constexpr u32 DepositBits(u32 val) { +constexpr u32 ExtractBits(u32 val) { u32 mask = mask_; u32 res = 0; for (u32 bb = 1; mask; bb += bb) { - u32 neg_mask = 0 - mask; - if (val & bb) - res |= mask & neg_mask; + const u32 lowest_bit = mask & (0 - mask); + if (val & lowest_bit) + res |= bb; mask &= mask - 1; } return res; @@ -40,7 +40,7 @@ template struct MatcherArg { template u32 Get() { - return DepositBits()>(instruction); + return ExtractBits()>(instruction); } u32 instruction; @@ -52,38 +52,94 @@ struct Matcher { std::function fn; }; +// pipeline flush cost for a PC write that program flow prediction cannot handle +// (ARM DDI 0211: refilling the 8-stage pipeline costs 5 to 7 cycles) +constexpr u64 pipeline_flush_cycles = 7; +// Indirect branches through a register other than lr hit the BTAC only when the +// target is stable; charge the average of a fold and a flush +constexpr u64 indirect_branch_cycles = 4; + u64 DataProcessing_imm(auto i) { if (i.template Get<"d">() == 15) { - return 7; + return pipeline_flush_cycles; } return 1; } u64 DataProcessing_reg(auto i) { if (i.template Get<"d">() == 15) { - return 7; + return pipeline_flush_cycles; } return 1; } u64 DataProcessing_rsr(auto i) { if (i.template Get<"d">() == 15) { - return 8; + return pipeline_flush_cycles + 1; } return 2; } -u64 LoadStoreSingle_imm(auto) { - return 2; +// MOV (reg) has its own handler: a plain "MOV pc, lr" is a procedure return, +// predicted by the return stack +u64 MovReg(auto i) { + if (i.template Get<"d">() == 15) { + const bool is_return = i.template Get<"m">() == 14 && i.template Get<"v">() == 0 && + i.template Get<"r">() == 0; + return is_return ? 1 : pipeline_flush_cycles; + } + return 1; } -u64 LoadStoreSingle_reg(auto i) { - // TODO: Load PC - if (i.template Get<"u">() == 1 && i.template Get<"r">() == 0 && - (i.template Get<"v">() == 0 || i.template Get<"v">() == 2)) { +// Loads: 1 issue cycle, loads into PC are branches: "LDR pc, [sp...]" is a +// return-stack-predicted procedure return; anything else (jump tables) flushes +u64 Load_imm(auto i) { + if (i.template Get<"t">() == 15) { + return i.template Get<"n">() == 13 ? 1 : pipeline_flush_cycles - 1; + } + return 1; +} +u64 Load_lit(auto i) { + if (i.template Get<"t">() == 15) { + return pipeline_flush_cycles - 1; + } + return 1; +} +u64 Load_reg(auto i) { + if (i.template Get<"t">() == 15) { + return pipeline_flush_cycles - 1; + } + // Scaled register offset requires an extra cycle for the offset shift + return (i.template Get<"v">() == 0 && i.template Get<"r">() == 0) ? 1 : 2; +} +u64 Load_reg_noshift(auto i) { + if (i.template Get<"t">() == 15) { + return pipeline_flush_cycles - 1; + } + return 1; +} +u64 Store_imm(auto) { + return 1; +} +u64 Store_reg(auto i) { + return (i.template Get<"v">() == 0 && i.template Get<"r">() == 0) ? 1 : 2; +} +u64 Store_reg_noshift(auto) { + return 1; +} +// Two registers per cycle over the 64-bit data path, a pop including PC is a +// return-stack-predicted procedure return, so no branch penalty is added +u64 LoadStoreMultiple(auto i) { + return 1 + std::popcount(i.template Get<"x">()) / 2; +} +// VLDR/VSTR (p=1, w=0): single transfer, 1 cycle. VLDM/VSTM/VPUSH/VPOP: the imm8 +// field counts words; two words transfer per cycle +u64 VfpLoadStore(auto i) { + if (i.template Get<"p">() == 0 && i.template Get<"u">() == 0) { + // MCRR/MRRC to the VFP (VMOV to/from a core register pair) share this + // encoding space (P=U=0 is not a valid VFP load/store addressing mode) return 2; } - return 4; -} -u64 LoadStoreMultiple(auto i) { - // TODO: Load PC - return 1 + std::popcount(i.template Get<"x">()) / 2; + if (i.template Get<"p">() == 1 && i.template Get<"w">() == 0) { + return 1; + } + return 1 + i.template Get<"v">() / 2; } #define INST(NAME, BS, CYCLES) \ @@ -97,14 +153,27 @@ u64 LoadStoreMultiple(auto i) { const std::array arm_matchers{ // clang-format off - // Branch instructions - INST("BLX (imm)", "1111101hvvvvvvvvvvvvvvvvvvvvvvvv", 5) // v5 - INST("BLX (reg)", "cccc000100101111111111110011mmmm", 6) // v5 - INST("B", "cccc1010vvvvvvvvvvvvvvvvvvvvvvvv", 4) // v1 - INST("BL", "cccc1011vvvvvvvvvvvvvvvvvvvvvvvv", 4) // v1 - INST("BX", "cccc000100101111111111110001mmmm", 5) // v4T + // branch instructions + // direct branches and calls are BTAC/statically predicted (folded when + // predicted; we charge the issue slot), BX lr is a return-stack-predicted + // return; indirect branches through other registers depend on target + // stability + INST("BLX (imm)", "1111101hvvvvvvvvvvvvvvvvvvvvvvvv", 1) // v5 + INST("BLX (reg)", "cccc000100101111111111110011mmmm", 2) // v5 + INST("B", "cccc1010vvvvvvvvvvvvvvvvvvvvvvvv", 1) // v1 + INST("BL", "cccc1011vvvvvvvvvvvvvvvvvvvvvvvv", 1) // v1 + INST("BX", "cccc000100101111111111110001mmmm", (i.template Get<"m">() == 14 ? 1 : indirect_branch_cycles)) // v4T INST("BXJ", "cccc000100101111111111110010mmmm", 1) // v5J + // floating-point (VFP11) instructions + // must precede the generic coprocessor entries below, Bit 8 selects double + // precision. Divide/sqrt block the non-pipelined DS unit for their full + // throughput; other data-processing operations are pipelined + INST("VDIV", "cccc11101-00--------101z-0-0----", (i.template Get<"z">() ? 29 : 15)) // VFPv2 + INST("VSQRT", "cccc11101-110001----101z11-0----", (i.template Get<"z">() ? 29 : 15)) // VFPv2 + INST("VFP DP op", "cccc1110------------101z---0----", (i.template Get<"z">() ? 2 : 1)) // VFPv2 + INST("VFP load/store", "cccc110pu-w---------101-vvvvvvvv", VfpLoadStore(i)) // VFPv2 + // Coprocessor instructions INST("CDP", "cccc1110ooooNNNNDDDDppppooo0MMMM", 1) // v2 (CDP2: v5) INST("LDC", "cccc110pudw1nnnnDDDDppppvvvvvvvv", 1) // v2 (LDC2: v5) @@ -137,7 +206,7 @@ const std::array arm_matchers{ INST("EOR (reg)", "cccc0000001Snnnnddddvvvvvrr0mmmm", DataProcessing_reg(i)) // v1 INST("EOR (rsr)", "cccc0000001Snnnnddddssss0rr1mmmm", DataProcessing_rsr(i)) // v1 INST("MOV (imm)", "cccc0011101S0000ddddrrrrvvvvvvvv", DataProcessing_imm(i)) // v1 - INST("MOV (reg)", "cccc0001101S0000ddddvvvvvrr0mmmm", DataProcessing_reg(i)) // v1 + INST("MOV (reg)", "cccc0001101S0000ddddvvvvvrr0mmmm", MovReg(i)) // v1 INST("MOV (rsr)", "cccc0001101S0000ddddssss0rr1mmmm", DataProcessing_rsr(i)) // v1 INST("MVN (imm)", "cccc0011111S0000ddddrrrrvvvvvvvv", DataProcessing_imm(i)) // v1 INST("MVN (reg)", "cccc0001111S0000ddddvvvvvrr0mmmm", DataProcessing_reg(i)) // v1 @@ -213,32 +282,32 @@ const std::array arm_matchers{ INST("STRBT (A2)", "----0110-110---------------0----", 1) // v1 INST("STRT (A1)", "----0100-010--------------------", 1) // v1 INST("STRT (A2)", "----0110-010---------------0----", 1) // v1 - INST("LDR (lit)", "cccc0101u0011111ttttvvvvvvvvvvvv", LoadStoreSingle_imm(i)) // v1 - INST("LDR (imm)", "cccc010pu0w1nnnnttttvvvvvvvvvvvv", LoadStoreSingle_imm(i)) // v1 - INST("LDR (reg)", "cccc011pu0w1nnnnttttvvvvvrr0mmmm", LoadStoreSingle_reg(i)) // v1 - INST("LDRB (lit)", "cccc0101u1011111ttttvvvvvvvvvvvv", LoadStoreSingle_imm(i)) // v1 - INST("LDRB (imm)", "cccc010pu1w1nnnnttttvvvvvvvvvvvv", LoadStoreSingle_imm(i)) // v1 - INST("LDRB (reg)", "cccc011pu1w1nnnnttttvvvvvrr0mmmm", LoadStoreSingle_reg(i)) // v1 - INST("LDRD (lit)", "cccc0001u1001111ttttvvvv1101vvvv", LoadStoreSingle_imm(i)) // v5E - INST("LDRD (imm)", "cccc000pu1w0nnnnttttvvvv1101vvvv", LoadStoreSingle_imm(i)) // v5E - INST("LDRD (reg)", "cccc000pu0w0nnnntttt00001101mmmm", LoadStoreSingle_reg(i)) // v5E - INST("LDRH (lit)", "cccc000pu1w11111ttttvvvv1011vvvv", LoadStoreSingle_imm(i)) // v4 - INST("LDRH (imm)", "cccc000pu1w1nnnnttttvvvv1011vvvv", LoadStoreSingle_imm(i)) // v4 - INST("LDRH (reg)", "cccc000pu0w1nnnntttt00001011mmmm", LoadStoreSingle_reg(i)) // v4 - INST("LDRSB (lit)", "cccc0001u1011111ttttvvvv1101vvvv", LoadStoreSingle_imm(i)) // v4 - INST("LDRSB (imm)", "cccc000pu1w1nnnnttttvvvv1101vvvv", LoadStoreSingle_imm(i)) // v4 - INST("LDRSB (reg)", "cccc000pu0w1nnnntttt00001101mmmm", LoadStoreSingle_reg(i)) // v4 - INST("LDRSH (lit)", "cccc0001u1011111ttttvvvv1111vvvv", LoadStoreSingle_imm(i)) // v4 - INST("LDRSH (imm)", "cccc000pu1w1nnnnttttvvvv1111vvvv", LoadStoreSingle_imm(i)) // v4 - INST("LDRSH (reg)", "cccc000pu0w1nnnntttt00001111mmmm", LoadStoreSingle_reg(i)) // v4 - INST("STR (imm)", "cccc010pu0w0nnnnttttvvvvvvvvvvvv", LoadStoreSingle_imm(i)) // v1 - INST("STR (reg)", "cccc011pu0w0nnnnttttvvvvvrr0mmmm", LoadStoreSingle_reg(i)) // v1 - INST("STRB (imm)", "cccc010pu1w0nnnnttttvvvvvvvvvvvv", LoadStoreSingle_imm(i)) // v1 - INST("STRB (reg)", "cccc011pu1w0nnnnttttvvvvvrr0mmmm", LoadStoreSingle_reg(i)) // v1 - INST("STRD (imm)", "cccc000pu1w0nnnnttttvvvv1111vvvv", LoadStoreSingle_imm(i)) // v5E - INST("STRD (reg)", "cccc000pu0w0nnnntttt00001111mmmm", LoadStoreSingle_reg(i)) // v5E - INST("STRH (imm)", "cccc000pu1w0nnnnttttvvvv1011vvvv", LoadStoreSingle_imm(i)) // v4 - INST("STRH (reg)", "cccc000pu0w0nnnntttt00001011mmmm", LoadStoreSingle_reg(i)) // v4 + INST("LDR (lit)", "cccc0101u0011111ttttvvvvvvvvvvvv", Load_lit(i)) // v1 + INST("LDR (imm)", "cccc010pu0w1nnnnttttvvvvvvvvvvvv", Load_imm(i)) // v1 + INST("LDR (reg)", "cccc011pu0w1nnnnttttvvvvvrr0mmmm", Load_reg(i)) // v1 + INST("LDRB (lit)", "cccc0101u1011111ttttvvvvvvvvvvvv", Load_lit(i)) // v1 + INST("LDRB (imm)", "cccc010pu1w1nnnnttttvvvvvvvvvvvv", Load_imm(i)) // v1 + INST("LDRB (reg)", "cccc011pu1w1nnnnttttvvvvvrr0mmmm", Load_reg(i)) // v1 + INST("LDRD (lit)", "cccc0001u1001111ttttvvvv1101vvvv", 1) // v5E + INST("LDRD (imm)", "cccc000pu1w0nnnnttttvvvv1101vvvv", 1) // v5E + INST("LDRD (reg)", "cccc000pu0w0nnnntttt00001101mmmm", 1) // v5E + INST("LDRH (lit)", "cccc000pu1w11111ttttvvvv1011vvvv", Load_reg_noshift(i)) // v4 + INST("LDRH (imm)", "cccc000pu1w1nnnnttttvvvv1011vvvv", Load_reg_noshift(i)) // v4 + INST("LDRH (reg)", "cccc000pu0w1nnnntttt00001011mmmm", Load_reg_noshift(i)) // v4 + INST("LDRSB (lit)", "cccc0001u1011111ttttvvvv1101vvvv", Load_reg_noshift(i)) // v4 + INST("LDRSB (imm)", "cccc000pu1w1nnnnttttvvvv1101vvvv", Load_reg_noshift(i)) // v4 + INST("LDRSB (reg)", "cccc000pu0w1nnnntttt00001101mmmm", Load_reg_noshift(i)) // v4 + INST("LDRSH (lit)", "cccc0001u1011111ttttvvvv1111vvvv", Load_reg_noshift(i)) // v4 + INST("LDRSH (imm)", "cccc000pu1w1nnnnttttvvvv1111vvvv", Load_reg_noshift(i)) // v4 + INST("LDRSH (reg)", "cccc000pu0w1nnnntttt00001111mmmm", Load_reg_noshift(i)) // v4 + INST("STR (imm)", "cccc010pu0w0nnnnttttvvvvvvvvvvvv", Store_imm(i)) // v1 + INST("STR (reg)", "cccc011pu0w0nnnnttttvvvvvrr0mmmm", Store_reg(i)) // v1 + INST("STRB (imm)", "cccc010pu1w0nnnnttttvvvvvvvvvvvv", Store_imm(i)) // v1 + INST("STRB (reg)", "cccc011pu1w0nnnnttttvvvvvrr0mmmm", Store_reg(i)) // v1 + INST("STRD (imm)", "cccc000pu1w0nnnnttttvvvv1111vvvv", 1) // v5E + INST("STRD (reg)", "cccc000pu0w0nnnntttt00001111mmmm", 1) // v5E + INST("STRH (imm)", "cccc000pu1w0nnnnttttvvvv1011vvvv", Store_reg_noshift(i)) // v4 + INST("STRH (reg)", "cccc000pu0w0nnnntttt00001011mmmm", Store_reg_noshift(i)) // v4 // Load/Store Multiple instructions INST("LDM", "cccc100010w1nnnnxxxxxxxxxxxxxxxx", LoadStoreMultiple(i)) // v1 @@ -370,7 +439,7 @@ const std::array arm_matchers{ // clang-format on }; -const std::array thumb_matchers{ +const std::array thumb16_matchers{ // clang-format off // Shift (immediate) add, subtract, move and compare instructions @@ -400,7 +469,7 @@ const std::array thumb_matchers{ INST("CMP (reg, T1)", "0100001010mmmnnn", 1) INST("CMN (reg)", "0100001011mmmnnn", 1) INST("ORR (reg)", "0100001100mmmddd", 1) - INST("MUL (reg)", "0100001101nnnddd", 1) + INST("MUL (reg)", "0100001101nnnddd", 2) INST("BIC (reg)", "0100001110mmmddd", 1) INST("MVN (reg)", "0100001111mmmddd", 1) @@ -410,23 +479,23 @@ const std::array thumb_matchers{ INST("MOV (reg)", "01000110Dmmmmddd", 1) // v4T, Low regs: v6 // Store/Load single data item instructions - INST("LDR (literal)", "01001tttvvvvvvvv", 2) - INST("STR (reg)", "0101000mmmnnnttt", 2) - INST("STRH (reg)", "0101001mmmnnnttt", 2) - INST("STRB (reg)", "0101010mmmnnnttt", 2) - INST("LDRSB (reg)", "0101011mmmnnnttt", 2) - INST("LDR (reg)", "0101100mmmnnnttt", 2) - INST("LDRH (reg)", "0101101mmmnnnttt", 2) - INST("LDRB (reg)", "0101110mmmnnnttt", 2) - INST("LDRSH (reg)", "0101111mmmnnnttt", 2) - INST("STR (imm, T1)", "01100vvvvvnnnttt", 2) - INST("LDR (imm, T1)", "01101vvvvvnnnttt", 2) - INST("STRB (imm)", "01110vvvvvnnnttt", 2) - INST("LDRB (imm)", "01111vvvvvnnnttt", 2) - INST("STRH (imm)", "10000vvvvvnnnttt", 2) - INST("LDRH (imm)", "10001vvvvvnnnttt", 2) - INST("STR (imm, T2)", "10010tttvvvvvvvv", 2) - INST("LDR (imm, T2)", "10011tttvvvvvvvv", 2) + INST("LDR (literal)", "01001tttvvvvvvvv", 1) + INST("STR (reg)", "0101000mmmnnnttt", 1) + INST("STRH (reg)", "0101001mmmnnnttt", 1) + INST("STRB (reg)", "0101010mmmnnnttt", 1) + INST("LDRSB (reg)", "0101011mmmnnnttt", 1) + INST("LDR (reg)", "0101100mmmnnnttt", 1) + INST("LDRH (reg)", "0101101mmmnnnttt", 1) + INST("LDRB (reg)", "0101110mmmnnnttt", 1) + INST("LDRSH (reg)", "0101111mmmnnnttt", 1) + INST("STR (imm, T1)", "01100vvvvvnnnttt", 1) + INST("LDR (imm, T1)", "01101vvvvvnnnttt", 1) + INST("STRB (imm)", "01110vvvvvnnnttt", 1) + INST("LDRB (imm)", "01111vvvvvnnnttt", 1) + INST("STRH (imm)", "10000vvvvvnnnttt", 1) + INST("LDRH (imm)", "10001vvvvvnnnttt", 1) + INST("STR (imm, T2)", "10010tttvvvvvvvv", 1) + INST("LDR (imm, T2)", "10011tttvvvvvvvv", 1) // Generate relative address instructions INST("ADR", "10100dddvvvvvvvv", 1) @@ -456,14 +525,22 @@ const std::array thumb_matchers{ INST("LDMIA", "11001nnnxxxxxxxx", LoadStoreMultiple(i)) // Branch instructions - INST("BX", "010001110mmmm000", 5) // v4T - INST("BLX (reg)", "010001111mmmm000", 6) // v5T + INST("BX", "010001110mmmm000", (i.template Get<"m">() == 14 ? 1 : indirect_branch_cycles)) // v4T + INST("BLX (reg)", "010001111mmmm000", 2) // v5T INST("UDF", "11011110--------", 8) INST("SVC", "11011111xxxxxxxx", 8) - INST("B (T1)", "1101ccccvvvvvvvv", 4) - INST("B (T2)", "11100vvvvvvvvvvv", 4) - INST("BL (imm)", "11110Svvvvvvvvvv11j1jvvvvvvvvvvv", 4) // v4T - INST("BLX (imm)", "11110Svvvvvvvvvv11j0jvvvvvvvvvvv", 5) // v5T + INST("B (T1)", "1101ccccvvvvvvvv", 1) + INST("B (T2)", "11100vvvvvvvvvvv", 1) + + // clang-format on +}; + +const std::array thumb32_matchers{ + // clang-format off + + // Branch instructions (the only 32-bit encodings on ARMv6: BL/BLX prefix pairs) + INST("BL (imm)", "11110Svvvvvvvvvv11j1jvvvvvvvvvvv", 1) // v4T + INST("BLX (imm)", "11110Svvvvvvvvvv11j0jvvvvvvvvvvv", 1) // v5T // clang-format on }; @@ -473,14 +550,30 @@ const std::array thumb_matchers{ namespace Core { u64 TicksForInstruction(bool is_thumb, u32 instruction) { - if (is_thumb) { - return 1; - } - const auto matches_instruction = [instruction](const auto& matcher) { return (instruction & matcher.mask) == matcher.expect; }; + if (is_thumb) { + // dynarmic passes 16-bit Thumb instructions zero-extended and 32-bit ones + // (BL/BLX prefix+suffix pairs on ARMv6) with the first halfword in the + // upper 16 bits + if ((instruction >> 16) != 0) { + auto iter = + std::find_if(thumb32_matchers.begin(), thumb32_matchers.end(), matches_instruction); + if (iter != thumb32_matchers.end()) { + return iter->fn(instruction); + } + return 1; + } + auto iter = + std::find_if(thumb16_matchers.begin(), thumb16_matchers.end(), matches_instruction); + if (iter != thumb16_matchers.end()) { + return iter->fn(instruction); + } + return 1; + } + auto iter = std::find_if(arm_matchers.begin(), arm_matchers.end(), matches_instruction); if (iter != arm_matchers.end()) { return iter->fn(instruction);