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<field> 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
This commit is contained in:
ApfelTeeSaft
2026-07-07 16:33:36 +02:00
parent 3eb504d67c
commit 934c846d12
9 changed files with 209 additions and 153 deletions
-1
View File
@@ -9,7 +9,6 @@ foreach(KEY IN ITEMS
"use_artic_base_controller" "use_artic_base_controller"
"enable_gamemode" "enable_gamemode"
"use_cpu_jit" "use_cpu_jit"
"cpu_legacy_instruction_ticks"
"cpu_clock_percentage" "cpu_clock_percentage"
"is_new_3ds" "is_new_3ds"
"lle_applets" "lle_applets"
-2
View File
@@ -474,7 +474,6 @@ void QtConfig::ReadCoreValues() {
qt_config->beginGroup(QStringLiteral("Core")); qt_config->beginGroup(QStringLiteral("Core"));
ReadGlobalSetting(Settings::values.cpu_clock_percentage); ReadGlobalSetting(Settings::values.cpu_clock_percentage);
ReadGlobalSetting(Settings::values.cpu_legacy_instruction_ticks);
if (global) { if (global) {
ReadBasicSetting(Settings::values.use_cpu_jit); ReadBasicSetting(Settings::values.use_cpu_jit);
@@ -1072,7 +1071,6 @@ void QtConfig::SaveCoreValues() {
qt_config->beginGroup(QStringLiteral("Core")); qt_config->beginGroup(QStringLiteral("Core"));
WriteGlobalSetting(Settings::values.cpu_clock_percentage); WriteGlobalSetting(Settings::values.cpu_clock_percentage);
WriteGlobalSetting(Settings::values.cpu_legacy_instruction_ticks);
if (global) { if (global) {
WriteBasicSetting(Settings::values.use_cpu_jit); WriteBasicSetting(Settings::values.use_cpu_jit);
@@ -143,8 +143,6 @@ void ConfigureDebug::SetConfiguration() {
ui->toggle_renderer_debug->setChecked(Settings::values.renderer_debug.GetValue()); ui->toggle_renderer_debug->setChecked(Settings::values.renderer_debug.GetValue());
ui->toggle_pica_debugging->setChecked(Settings::values.pica_debugging.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_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::IsConfiguringGlobal()) {
if (Settings::values.cpu_clock_percentage.UsingGlobal()) { 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.dump_command_buffers = ui->toggle_dump_command_buffers->isChecked();
Settings::values.instant_debug_log = ui->instant_debug_log->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( ConfigurationShared::ApplyPerGameSetting(
&Settings::values.cpu_clock_percentage, ui->clock_speed_combo, &Settings::values.cpu_clock_percentage, ui->clock_speed_combo,
[this](s32) { return SliderToSettings(ui->slider_clock_speed->value()); }); [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 // Block the global settings if a game is currently running that overrides them
if (Settings::IsConfiguringGlobal()) { if (Settings::IsConfiguringGlobal()) {
ui->slider_clock_speed->setEnabled(Settings::values.cpu_clock_percentage.UsingGlobal()); 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; 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<int>(&QComboBox::activated), this, [this](int index) { connect(ui->clock_speed_combo, qOverload<int>(&QComboBox::activated), this, [this](int index) {
ui->slider_clock_speed->setEnabled(index == 1); ui->slider_clock_speed->setEnabled(index == 1);
ConfigurationShared::SetHighlight(ui->clock_speed_widget, index == 1); ConfigurationShared::SetHighlight(ui->clock_speed_widget, index == 1);
@@ -7,10 +7,6 @@
#include <memory> #include <memory>
#include <QWidget> #include <QWidget>
namespace ConfigurationShared {
enum class CheckState;
}
namespace Ui { namespace Ui {
class ConfigureDebug; class ConfigureDebug;
} }
@@ -30,5 +26,4 @@ public:
private: private:
std::unique_ptr<Ui::ConfigureDebug> ui; std::unique_ptr<Ui::ConfigureDebug> ui;
bool is_powered_on; bool is_powered_on;
ConfigurationShared::CheckState cpu_legacy_instruction_ticks;
}; };
+35 -45
View File
@@ -61,11 +61,11 @@
</layout> </layout>
</item> </item>
<item> <item>
<widget class="QCheckBox" name="debug_next_process"> <widget class="QCheckBox" name="debug_next_process">
<property name="text"> <property name="text">
<string>Pause next non-sysmodule process at start</string> <string>Pause next non-sysmodule process at start</string>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</widget> </widget>
@@ -125,14 +125,14 @@
</layout> </layout>
</item> </item>
<item> <item>
<widget class="QCheckBox" name="instant_debug_log"> <widget class="QCheckBox" name="instant_debug_log">
<property name="text"> <property name="text">
<string>Flush log output on every message</string> <string>Flush log output on every message</string>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;body&gt;Immediately commits the debug log to file. Use this if Azahar crashes and the log output is being cut.&lt;br&gt;Enabling this feature will decrease performance, only use it for debugging purposes.&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;body&gt;Immediately commits the debug log to file. Use this if Azahar crashes and the log output is being cut.&lt;br&gt;Enabling this feature will decrease performance, only use it for debugging purposes.&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</widget> </widget>
@@ -226,13 +226,13 @@
<item row="2" column="0"> <item row="2" column="0">
<widget class="QLabel" name="label_cpu_clock_info"> <widget class="QLabel" name="label_cpu_clock_info">
<property name="text"> <property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;Underclocking can increase performance but may cause the application to freeze.&lt;br/&gt;Overclocking may reduce lag in applications but also might cause freezes&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;Underclocking can increase performance but may cause the application to freeze.&lt;br/&gt;Overclocking may reduce lag in applications but also might cause freezes&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="textFormat"> <property name="textFormat">
<enum>Qt::RichText</enum> <enum>Qt::RichText</enum>
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="0"> <item row="3" column="0">
<widget class="QCheckBox" name="toggle_cpu_jit"> <widget class="QCheckBox" name="toggle_cpu_jit">
<property name="toolTip"> <property name="toolTip">
@@ -243,16 +243,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="7" column="0">
<widget class="QCheckBox" name="toggle_cpu_legacy_instruction_ticks">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Use legacy CPU cycle timing (1 tick per instruction)</string>
</property>
</widget>
</item>
<item row="4" column="0"> <item row="4" column="0">
<widget class="QCheckBox" name="toggle_renderer_debug"> <widget class="QCheckBox" name="toggle_renderer_debug">
<property name="text"> <property name="text">
@@ -260,13 +250,13 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="0"> <item row="5" column="0">
<widget class="QCheckBox" name="toggle_pica_debugging"> <widget class="QCheckBox" name="toggle_pica_debugging">
<property name="text"> <property name="text">
<string>Enable PICA200 debugging</string> <string>Enable PICA200 debugging</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="6" column="0"> <item row="6" column="0">
<widget class="QCheckBox" name="toggle_dump_command_buffers"> <widget class="QCheckBox" name="toggle_dump_command_buffers">
<property name="text"> <property name="text">
@@ -314,14 +304,14 @@
</widget> </widget>
</item> </item>
<item row="4" column="0"> <item row="4" column="0">
<widget class="QCheckBox" name="enable_rpc_server"> <widget class="QCheckBox" name="enable_rpc_server">
<property name="text"> <property name="text">
<string>Enable RPC server</string> <string>Enable RPC server</string>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="0"> <item row="5" column="0">
<widget class="QCheckBox" name="break_on_unmapped_memory_access"> <widget class="QCheckBox" name="break_on_unmapped_memory_access">
-2
View File
@@ -88,8 +88,6 @@ void LogSettings() {
LOG_INFO(Config, "Azahar Configuration:"); LOG_INFO(Config, "Azahar Configuration:");
log_setting("Core_UseCpuJit", values.use_cpu_jit.GetValue()); log_setting("Core_UseCpuJit", values.use_cpu_jit.GetValue());
log_setting("Core_CPUClockPercentage", values.cpu_clock_percentage.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("Controller_UseArticController", values.use_artic_base_controller.GetValue());
log_setting("Renderer_UseGLES", values.use_gles.GetValue()); log_setting("Renderer_UseGLES", values.use_gles.GetValue());
log_setting("Renderer_GraphicsAPI", GetGraphicsAPIName(values.graphics_api.GetValue())); log_setting("Renderer_GraphicsAPI", GetGraphicsAPIName(values.graphics_api.GetValue()));
-2
View File
@@ -482,8 +482,6 @@ struct Values {
// Core // Core
Setting<bool> use_cpu_jit{true, Keys::use_cpu_jit}; Setting<bool> use_cpu_jit{true, Keys::use_cpu_jit};
SwitchableSetting<s32, true> cpu_clock_percentage{100, 5, 400, Keys::cpu_clock_percentage}; SwitchableSetting<s32, true> cpu_clock_percentage{100, 5, 400, Keys::cpu_clock_percentage};
SwitchableSetting<bool> cpu_legacy_instruction_ticks{false,
Keys::cpu_legacy_instruction_ticks};
SwitchableSetting<bool> is_new_3ds{true, Keys::is_new_3ds}; SwitchableSetting<bool> is_new_3ds{true, Keys::is_new_3ds};
SwitchableSetting<bool> lle_applets{true, Keys::lle_applets}; SwitchableSetting<bool> lle_applets{true, Keys::lle_applets};
SwitchableSetting<bool> deterministic_async_operations{false, SwitchableSetting<bool> deterministic_async_operations{false,
-4
View File
@@ -8,7 +8,6 @@
#include <dynarmic/interface/optimization_flags.h> #include <dynarmic/interface/optimization_flags.h>
#include "common/assert.h" #include "common/assert.h"
#include "common/microprofile.h" #include "common/microprofile.h"
#include "common/settings.h"
#include "core/arm/dynarmic/arm_dynarmic.h" #include "core/arm/dynarmic/arm_dynarmic.h"
#include "core/arm/dynarmic/arm_dynarmic_cp15.h" #include "core/arm/dynarmic/arm_dynarmic_cp15.h"
#include "core/arm/dynarmic/arm_exclusive_monitor.h" #include "core/arm/dynarmic/arm_exclusive_monitor.h"
@@ -160,9 +159,6 @@ public:
return static_cast<u64>(ticks <= 0 ? 0 : ticks); return static_cast<u64>(ticks <= 0 ? 0 : ticks);
} }
std::uint64_t GetTicksForCode(bool is_thumb, VAddr, std::uint32_t instruction) override { 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); return Core::TicksForInstruction(is_thumb, instruction);
} }
+174 -81
View File
@@ -24,13 +24,13 @@ constexpr u32 GetMatchingBitsFromStringLiteral() {
} }
template <u32 mask_> template <u32 mask_>
constexpr u32 DepositBits(u32 val) { constexpr u32 ExtractBits(u32 val) {
u32 mask = mask_; u32 mask = mask_;
u32 res = 0; u32 res = 0;
for (u32 bb = 1; mask; bb += bb) { for (u32 bb = 1; mask; bb += bb) {
u32 neg_mask = 0 - mask; const u32 lowest_bit = mask & (0 - mask);
if (val & bb) if (val & lowest_bit)
res |= mask & neg_mask; res |= bb;
mask &= mask - 1; mask &= mask - 1;
} }
return res; return res;
@@ -40,7 +40,7 @@ template <Common::StringLiteral haystack>
struct MatcherArg { struct MatcherArg {
template <Common::StringLiteral needle> template <Common::StringLiteral needle>
u32 Get() { u32 Get() {
return DepositBits<GetMatchingBitsFromStringLiteral<haystack, needle>()>(instruction); return ExtractBits<GetMatchingBitsFromStringLiteral<haystack, needle>()>(instruction);
} }
u32 instruction; u32 instruction;
@@ -52,38 +52,94 @@ struct Matcher {
std::function<u64(u32)> fn; std::function<u64(u32)> 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) { u64 DataProcessing_imm(auto i) {
if (i.template Get<"d">() == 15) { if (i.template Get<"d">() == 15) {
return 7; return pipeline_flush_cycles;
} }
return 1; return 1;
} }
u64 DataProcessing_reg(auto i) { u64 DataProcessing_reg(auto i) {
if (i.template Get<"d">() == 15) { if (i.template Get<"d">() == 15) {
return 7; return pipeline_flush_cycles;
} }
return 1; return 1;
} }
u64 DataProcessing_rsr(auto i) { u64 DataProcessing_rsr(auto i) {
if (i.template Get<"d">() == 15) { if (i.template Get<"d">() == 15) {
return 8; return pipeline_flush_cycles + 1;
} }
return 2; return 2;
} }
u64 LoadStoreSingle_imm(auto) { // MOV (reg) has its own handler: a plain "MOV pc, lr" is a procedure return,
return 2; // 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) { // Loads: 1 issue cycle, loads into PC are branches: "LDR pc, [sp...]" is a
// TODO: Load PC // return-stack-predicted procedure return; anything else (jump tables) flushes
if (i.template Get<"u">() == 1 && i.template Get<"r">() == 0 && u64 Load_imm(auto i) {
(i.template Get<"v">() == 0 || i.template Get<"v">() == 2)) { 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 2;
} }
return 4; if (i.template Get<"p">() == 1 && i.template Get<"w">() == 0) {
} return 1;
u64 LoadStoreMultiple(auto i) { }
// TODO: Load PC return 1 + i.template Get<"v">() / 2;
return 1 + std::popcount(i.template Get<"x">()) / 2;
} }
#define INST(NAME, BS, CYCLES) \ #define INST(NAME, BS, CYCLES) \
@@ -97,14 +153,27 @@ u64 LoadStoreMultiple(auto i) {
const std::array arm_matchers{ const std::array arm_matchers{
// clang-format off // clang-format off
// Branch instructions // branch instructions
INST("BLX (imm)", "1111101hvvvvvvvvvvvvvvvvvvvvvvvv", 5) // v5 // direct branches and calls are BTAC/statically predicted (folded when
INST("BLX (reg)", "cccc000100101111111111110011mmmm", 6) // v5 // predicted; we charge the issue slot), BX lr is a return-stack-predicted
INST("B", "cccc1010vvvvvvvvvvvvvvvvvvvvvvvv", 4) // v1 // return; indirect branches through other registers depend on target
INST("BL", "cccc1011vvvvvvvvvvvvvvvvvvvvvvvv", 4) // v1 // stability
INST("BX", "cccc000100101111111111110001mmmm", 5) // v4T 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 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 // Coprocessor instructions
INST("CDP", "cccc1110ooooNNNNDDDDppppooo0MMMM", 1) // v2 (CDP2: v5) INST("CDP", "cccc1110ooooNNNNDDDDppppooo0MMMM", 1) // v2 (CDP2: v5)
INST("LDC", "cccc110pudw1nnnnDDDDppppvvvvvvvv", 1) // v2 (LDC2: 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 (reg)", "cccc0000001Snnnnddddvvvvvrr0mmmm", DataProcessing_reg(i)) // v1
INST("EOR (rsr)", "cccc0000001Snnnnddddssss0rr1mmmm", DataProcessing_rsr(i)) // v1 INST("EOR (rsr)", "cccc0000001Snnnnddddssss0rr1mmmm", DataProcessing_rsr(i)) // v1
INST("MOV (imm)", "cccc0011101S0000ddddrrrrvvvvvvvv", DataProcessing_imm(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("MOV (rsr)", "cccc0001101S0000ddddssss0rr1mmmm", DataProcessing_rsr(i)) // v1
INST("MVN (imm)", "cccc0011111S0000ddddrrrrvvvvvvvv", DataProcessing_imm(i)) // v1 INST("MVN (imm)", "cccc0011111S0000ddddrrrrvvvvvvvv", DataProcessing_imm(i)) // v1
INST("MVN (reg)", "cccc0001111S0000ddddvvvvvrr0mmmm", DataProcessing_reg(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("STRBT (A2)", "----0110-110---------------0----", 1) // v1
INST("STRT (A1)", "----0100-010--------------------", 1) // v1 INST("STRT (A1)", "----0100-010--------------------", 1) // v1
INST("STRT (A2)", "----0110-010---------------0----", 1) // v1 INST("STRT (A2)", "----0110-010---------------0----", 1) // v1
INST("LDR (lit)", "cccc0101u0011111ttttvvvvvvvvvvvv", LoadStoreSingle_imm(i)) // v1 INST("LDR (lit)", "cccc0101u0011111ttttvvvvvvvvvvvv", Load_lit(i)) // v1
INST("LDR (imm)", "cccc010pu0w1nnnnttttvvvvvvvvvvvv", LoadStoreSingle_imm(i)) // v1 INST("LDR (imm)", "cccc010pu0w1nnnnttttvvvvvvvvvvvv", Load_imm(i)) // v1
INST("LDR (reg)", "cccc011pu0w1nnnnttttvvvvvrr0mmmm", LoadStoreSingle_reg(i)) // v1 INST("LDR (reg)", "cccc011pu0w1nnnnttttvvvvvrr0mmmm", Load_reg(i)) // v1
INST("LDRB (lit)", "cccc0101u1011111ttttvvvvvvvvvvvv", LoadStoreSingle_imm(i)) // v1 INST("LDRB (lit)", "cccc0101u1011111ttttvvvvvvvvvvvv", Load_lit(i)) // v1
INST("LDRB (imm)", "cccc010pu1w1nnnnttttvvvvvvvvvvvv", LoadStoreSingle_imm(i)) // v1 INST("LDRB (imm)", "cccc010pu1w1nnnnttttvvvvvvvvvvvv", Load_imm(i)) // v1
INST("LDRB (reg)", "cccc011pu1w1nnnnttttvvvvvrr0mmmm", LoadStoreSingle_reg(i)) // v1 INST("LDRB (reg)", "cccc011pu1w1nnnnttttvvvvvrr0mmmm", Load_reg(i)) // v1
INST("LDRD (lit)", "cccc0001u1001111ttttvvvv1101vvvv", LoadStoreSingle_imm(i)) // v5E INST("LDRD (lit)", "cccc0001u1001111ttttvvvv1101vvvv", 1) // v5E
INST("LDRD (imm)", "cccc000pu1w0nnnnttttvvvv1101vvvv", LoadStoreSingle_imm(i)) // v5E INST("LDRD (imm)", "cccc000pu1w0nnnnttttvvvv1101vvvv", 1) // v5E
INST("LDRD (reg)", "cccc000pu0w0nnnntttt00001101mmmm", LoadStoreSingle_reg(i)) // v5E INST("LDRD (reg)", "cccc000pu0w0nnnntttt00001101mmmm", 1) // v5E
INST("LDRH (lit)", "cccc000pu1w11111ttttvvvv1011vvvv", LoadStoreSingle_imm(i)) // v4 INST("LDRH (lit)", "cccc000pu1w11111ttttvvvv1011vvvv", Load_reg_noshift(i)) // v4
INST("LDRH (imm)", "cccc000pu1w1nnnnttttvvvv1011vvvv", LoadStoreSingle_imm(i)) // v4 INST("LDRH (imm)", "cccc000pu1w1nnnnttttvvvv1011vvvv", Load_reg_noshift(i)) // v4
INST("LDRH (reg)", "cccc000pu0w1nnnntttt00001011mmmm", LoadStoreSingle_reg(i)) // v4 INST("LDRH (reg)", "cccc000pu0w1nnnntttt00001011mmmm", Load_reg_noshift(i)) // v4
INST("LDRSB (lit)", "cccc0001u1011111ttttvvvv1101vvvv", LoadStoreSingle_imm(i)) // v4 INST("LDRSB (lit)", "cccc0001u1011111ttttvvvv1101vvvv", Load_reg_noshift(i)) // v4
INST("LDRSB (imm)", "cccc000pu1w1nnnnttttvvvv1101vvvv", LoadStoreSingle_imm(i)) // v4 INST("LDRSB (imm)", "cccc000pu1w1nnnnttttvvvv1101vvvv", Load_reg_noshift(i)) // v4
INST("LDRSB (reg)", "cccc000pu0w1nnnntttt00001101mmmm", LoadStoreSingle_reg(i)) // v4 INST("LDRSB (reg)", "cccc000pu0w1nnnntttt00001101mmmm", Load_reg_noshift(i)) // v4
INST("LDRSH (lit)", "cccc0001u1011111ttttvvvv1111vvvv", LoadStoreSingle_imm(i)) // v4 INST("LDRSH (lit)", "cccc0001u1011111ttttvvvv1111vvvv", Load_reg_noshift(i)) // v4
INST("LDRSH (imm)", "cccc000pu1w1nnnnttttvvvv1111vvvv", LoadStoreSingle_imm(i)) // v4 INST("LDRSH (imm)", "cccc000pu1w1nnnnttttvvvv1111vvvv", Load_reg_noshift(i)) // v4
INST("LDRSH (reg)", "cccc000pu0w1nnnntttt00001111mmmm", LoadStoreSingle_reg(i)) // v4 INST("LDRSH (reg)", "cccc000pu0w1nnnntttt00001111mmmm", Load_reg_noshift(i)) // v4
INST("STR (imm)", "cccc010pu0w0nnnnttttvvvvvvvvvvvv", LoadStoreSingle_imm(i)) // v1 INST("STR (imm)", "cccc010pu0w0nnnnttttvvvvvvvvvvvv", Store_imm(i)) // v1
INST("STR (reg)", "cccc011pu0w0nnnnttttvvvvvrr0mmmm", LoadStoreSingle_reg(i)) // v1 INST("STR (reg)", "cccc011pu0w0nnnnttttvvvvvrr0mmmm", Store_reg(i)) // v1
INST("STRB (imm)", "cccc010pu1w0nnnnttttvvvvvvvvvvvv", LoadStoreSingle_imm(i)) // v1 INST("STRB (imm)", "cccc010pu1w0nnnnttttvvvvvvvvvvvv", Store_imm(i)) // v1
INST("STRB (reg)", "cccc011pu1w0nnnnttttvvvvvrr0mmmm", LoadStoreSingle_reg(i)) // v1 INST("STRB (reg)", "cccc011pu1w0nnnnttttvvvvvrr0mmmm", Store_reg(i)) // v1
INST("STRD (imm)", "cccc000pu1w0nnnnttttvvvv1111vvvv", LoadStoreSingle_imm(i)) // v5E INST("STRD (imm)", "cccc000pu1w0nnnnttttvvvv1111vvvv", 1) // v5E
INST("STRD (reg)", "cccc000pu0w0nnnntttt00001111mmmm", LoadStoreSingle_reg(i)) // v5E INST("STRD (reg)", "cccc000pu0w0nnnntttt00001111mmmm", 1) // v5E
INST("STRH (imm)", "cccc000pu1w0nnnnttttvvvv1011vvvv", LoadStoreSingle_imm(i)) // v4 INST("STRH (imm)", "cccc000pu1w0nnnnttttvvvv1011vvvv", Store_reg_noshift(i)) // v4
INST("STRH (reg)", "cccc000pu0w0nnnntttt00001011mmmm", LoadStoreSingle_reg(i)) // v4 INST("STRH (reg)", "cccc000pu0w0nnnntttt00001011mmmm", Store_reg_noshift(i)) // v4
// Load/Store Multiple instructions // Load/Store Multiple instructions
INST("LDM", "cccc100010w1nnnnxxxxxxxxxxxxxxxx", LoadStoreMultiple(i)) // v1 INST("LDM", "cccc100010w1nnnnxxxxxxxxxxxxxxxx", LoadStoreMultiple(i)) // v1
@@ -370,7 +439,7 @@ const std::array arm_matchers{
// clang-format on // clang-format on
}; };
const std::array thumb_matchers{ const std::array thumb16_matchers{
// clang-format off // clang-format off
// Shift (immediate) add, subtract, move and compare instructions // Shift (immediate) add, subtract, move and compare instructions
@@ -400,7 +469,7 @@ const std::array thumb_matchers{
INST("CMP (reg, T1)", "0100001010mmmnnn", 1) INST("CMP (reg, T1)", "0100001010mmmnnn", 1)
INST("CMN (reg)", "0100001011mmmnnn", 1) INST("CMN (reg)", "0100001011mmmnnn", 1)
INST("ORR (reg)", "0100001100mmmddd", 1) INST("ORR (reg)", "0100001100mmmddd", 1)
INST("MUL (reg)", "0100001101nnnddd", 1) INST("MUL (reg)", "0100001101nnnddd", 2)
INST("BIC (reg)", "0100001110mmmddd", 1) INST("BIC (reg)", "0100001110mmmddd", 1)
INST("MVN (reg)", "0100001111mmmddd", 1) INST("MVN (reg)", "0100001111mmmddd", 1)
@@ -410,23 +479,23 @@ const std::array thumb_matchers{
INST("MOV (reg)", "01000110Dmmmmddd", 1) // v4T, Low regs: v6 INST("MOV (reg)", "01000110Dmmmmddd", 1) // v4T, Low regs: v6
// Store/Load single data item instructions // Store/Load single data item instructions
INST("LDR (literal)", "01001tttvvvvvvvv", 2) INST("LDR (literal)", "01001tttvvvvvvvv", 1)
INST("STR (reg)", "0101000mmmnnnttt", 2) INST("STR (reg)", "0101000mmmnnnttt", 1)
INST("STRH (reg)", "0101001mmmnnnttt", 2) INST("STRH (reg)", "0101001mmmnnnttt", 1)
INST("STRB (reg)", "0101010mmmnnnttt", 2) INST("STRB (reg)", "0101010mmmnnnttt", 1)
INST("LDRSB (reg)", "0101011mmmnnnttt", 2) INST("LDRSB (reg)", "0101011mmmnnnttt", 1)
INST("LDR (reg)", "0101100mmmnnnttt", 2) INST("LDR (reg)", "0101100mmmnnnttt", 1)
INST("LDRH (reg)", "0101101mmmnnnttt", 2) INST("LDRH (reg)", "0101101mmmnnnttt", 1)
INST("LDRB (reg)", "0101110mmmnnnttt", 2) INST("LDRB (reg)", "0101110mmmnnnttt", 1)
INST("LDRSH (reg)", "0101111mmmnnnttt", 2) INST("LDRSH (reg)", "0101111mmmnnnttt", 1)
INST("STR (imm, T1)", "01100vvvvvnnnttt", 2) INST("STR (imm, T1)", "01100vvvvvnnnttt", 1)
INST("LDR (imm, T1)", "01101vvvvvnnnttt", 2) INST("LDR (imm, T1)", "01101vvvvvnnnttt", 1)
INST("STRB (imm)", "01110vvvvvnnnttt", 2) INST("STRB (imm)", "01110vvvvvnnnttt", 1)
INST("LDRB (imm)", "01111vvvvvnnnttt", 2) INST("LDRB (imm)", "01111vvvvvnnnttt", 1)
INST("STRH (imm)", "10000vvvvvnnnttt", 2) INST("STRH (imm)", "10000vvvvvnnnttt", 1)
INST("LDRH (imm)", "10001vvvvvnnnttt", 2) INST("LDRH (imm)", "10001vvvvvnnnttt", 1)
INST("STR (imm, T2)", "10010tttvvvvvvvv", 2) INST("STR (imm, T2)", "10010tttvvvvvvvv", 1)
INST("LDR (imm, T2)", "10011tttvvvvvvvv", 2) INST("LDR (imm, T2)", "10011tttvvvvvvvv", 1)
// Generate relative address instructions // Generate relative address instructions
INST("ADR", "10100dddvvvvvvvv", 1) INST("ADR", "10100dddvvvvvvvv", 1)
@@ -456,14 +525,22 @@ const std::array thumb_matchers{
INST("LDMIA", "11001nnnxxxxxxxx", LoadStoreMultiple(i)) INST("LDMIA", "11001nnnxxxxxxxx", LoadStoreMultiple(i))
// Branch instructions // Branch instructions
INST("BX", "010001110mmmm000", 5) // v4T INST("BX", "010001110mmmm000", (i.template Get<"m">() == 14 ? 1 : indirect_branch_cycles)) // v4T
INST("BLX (reg)", "010001111mmmm000", 6) // v5T INST("BLX (reg)", "010001111mmmm000", 2) // v5T
INST("UDF", "11011110--------", 8) INST("UDF", "11011110--------", 8)
INST("SVC", "11011111xxxxxxxx", 8) INST("SVC", "11011111xxxxxxxx", 8)
INST("B (T1)", "1101ccccvvvvvvvv", 4) INST("B (T1)", "1101ccccvvvvvvvv", 1)
INST("B (T2)", "11100vvvvvvvvvvv", 4) INST("B (T2)", "11100vvvvvvvvvvv", 1)
INST("BL (imm)", "11110Svvvvvvvvvv11j1jvvvvvvvvvvv", 4) // v4T
INST("BLX (imm)", "11110Svvvvvvvvvv11j0jvvvvvvvvvvv", 5) // v5T // 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 // clang-format on
}; };
@@ -473,14 +550,30 @@ const std::array thumb_matchers{
namespace Core { namespace Core {
u64 TicksForInstruction(bool is_thumb, u32 instruction) { u64 TicksForInstruction(bool is_thumb, u32 instruction) {
if (is_thumb) {
return 1;
}
const auto matches_instruction = [instruction](const auto& matcher) { const auto matches_instruction = [instruction](const auto& matcher) {
return (instruction & matcher.mask) == matcher.expect; 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); auto iter = std::find_if(arm_matchers.begin(), arm_matchers.end(), matches_instruction);
if (iter != arm_matchers.end()) { if (iter != arm_matchers.end()) {
return iter->fn(instruction); return iter->fn(instruction);