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"
"enable_gamemode"
"use_cpu_jit"
"cpu_legacy_instruction_ticks"
"cpu_clock_percentage"
"is_new_3ds"
"lle_applets"
-2
View File
@@ -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);
@@ -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<int>(&QComboBox::activated), this, [this](int index) {
ui->slider_clock_speed->setEnabled(index == 1);
ConfigurationShared::SetHighlight(ui->clock_speed_widget, index == 1);
@@ -7,10 +7,6 @@
#include <memory>
#include <QWidget>
namespace ConfigurationShared {
enum class CheckState;
}
namespace Ui {
class ConfigureDebug;
}
@@ -30,5 +26,4 @@ public:
private:
std::unique_ptr<Ui::ConfigureDebug> ui;
bool is_powered_on;
ConfigurationShared::CheckState cpu_legacy_instruction_ticks;
};
+35 -45
View File
@@ -61,11 +61,11 @@
</layout>
</item>
<item>
<widget class="QCheckBox" name="debug_next_process">
<property name="text">
<string>Pause next non-sysmodule process at start</string>
</property>
</widget>
<widget class="QCheckBox" name="debug_next_process">
<property name="text">
<string>Pause next non-sysmodule process at start</string>
</property>
</widget>
</item>
</layout>
</widget>
@@ -125,14 +125,14 @@
</layout>
</item>
<item>
<widget class="QCheckBox" name="instant_debug_log">
<property name="text">
<string>Flush log output on every message</string>
</property>
<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>
</property>
</widget>
<widget class="QCheckBox" name="instant_debug_log">
<property name="text">
<string>Flush log output on every message</string>
</property>
<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>
</property>
</widget>
</item>
</layout>
</widget>
@@ -226,13 +226,13 @@
<item row="2" column="0">
<widget class="QLabel" name="label_cpu_clock_info">
<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>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
</widget>
</item>
<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 name="textFormat">
<enum>Qt::RichText</enum>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="toggle_cpu_jit">
<property name="toolTip">
@@ -243,16 +243,6 @@
</property>
</widget>
</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">
<widget class="QCheckBox" name="toggle_renderer_debug">
<property name="text">
@@ -260,13 +250,13 @@
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QCheckBox" name="toggle_pica_debugging">
<property name="text">
<string>Enable PICA200 debugging</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QCheckBox" name="toggle_pica_debugging">
<property name="text">
<string>Enable PICA200 debugging</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QCheckBox" name="toggle_dump_command_buffers">
<property name="text">
@@ -314,14 +304,14 @@
</widget>
</item>
<item row="4" column="0">
<widget class="QCheckBox" name="enable_rpc_server">
<property name="text">
<string>Enable RPC server</string>
</property>
<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>
</property>
</widget>
<widget class="QCheckBox" name="enable_rpc_server">
<property name="text">
<string>Enable RPC server</string>
</property>
<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>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QCheckBox" name="break_on_unmapped_memory_access">
-2
View File
@@ -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()));
-2
View File
@@ -482,8 +482,6 @@ struct Values {
// Core
Setting<bool> use_cpu_jit{true, Keys::use_cpu_jit};
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> lle_applets{true, Keys::lle_applets};
SwitchableSetting<bool> deterministic_async_operations{false,
-4
View File
@@ -8,7 +8,6 @@
#include <dynarmic/interface/optimization_flags.h>
#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<u64>(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);
}
+174 -81
View File
@@ -24,13 +24,13 @@ constexpr u32 GetMatchingBitsFromStringLiteral() {
}
template <u32 mask_>
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 <Common::StringLiteral haystack>
struct MatcherArg {
template <Common::StringLiteral needle>
u32 Get() {
return DepositBits<GetMatchingBitsFromStringLiteral<haystack, needle>()>(instruction);
return ExtractBits<GetMatchingBitsFromStringLiteral<haystack, needle>()>(instruction);
}
u32 instruction;
@@ -52,38 +52,94 @@ struct Matcher {
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) {
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);