mirror of
https://github.com/izzy2lost/dolphin.git
synced 2026-06-19 01:16:48 -07:00
Merge pull request #12359 from mitaclaw/code-diff-dialog-refresh
BranchWatchDialog: A Total Replacement for CodeDiffDialog
This commit is contained in:
@@ -75,6 +75,8 @@
|
||||
#define DUMP_AUDIO_DIR "Audio"
|
||||
#define DUMP_DSP_DIR "DSP"
|
||||
#define DUMP_SSL_DIR "SSL"
|
||||
#define DUMP_DEBUG_DIR "Debug"
|
||||
#define DUMP_DEBUG_BRANCHWATCH_DIR "BranchWatch"
|
||||
#define LOGS_DIR "Logs"
|
||||
#define MAIL_LOGS_DIR "Mail"
|
||||
#define SHADERS_DIR "Shaders"
|
||||
|
||||
@@ -856,6 +856,9 @@ static void RebuildUserDirectories(unsigned int dir_index)
|
||||
s_user_paths[D_DUMPTEXTURES_IDX] = s_user_paths[D_DUMP_IDX] + DUMP_TEXTURES_DIR DIR_SEP;
|
||||
s_user_paths[D_DUMPDSP_IDX] = s_user_paths[D_DUMP_IDX] + DUMP_DSP_DIR DIR_SEP;
|
||||
s_user_paths[D_DUMPSSL_IDX] = s_user_paths[D_DUMP_IDX] + DUMP_SSL_DIR DIR_SEP;
|
||||
s_user_paths[D_DUMPDEBUG_IDX] = s_user_paths[D_DUMP_IDX] + DUMP_DEBUG_DIR DIR_SEP;
|
||||
s_user_paths[D_DUMPDEBUG_BRANCHWATCH_IDX] =
|
||||
s_user_paths[D_DUMPDEBUG_IDX] + DUMP_DEBUG_BRANCHWATCH_DIR DIR_SEP;
|
||||
s_user_paths[D_LOGS_IDX] = s_user_paths[D_USER_IDX] + LOGS_DIR DIR_SEP;
|
||||
s_user_paths[D_MAILLOGS_IDX] = s_user_paths[D_LOGS_IDX] + MAIL_LOGS_DIR DIR_SEP;
|
||||
s_user_paths[D_THEMES_IDX] = s_user_paths[D_USER_IDX] + THEMES_DIR DIR_SEP;
|
||||
@@ -932,6 +935,9 @@ static void RebuildUserDirectories(unsigned int dir_index)
|
||||
s_user_paths[D_DUMPTEXTURES_IDX] = s_user_paths[D_DUMP_IDX] + DUMP_TEXTURES_DIR DIR_SEP;
|
||||
s_user_paths[D_DUMPDSP_IDX] = s_user_paths[D_DUMP_IDX] + DUMP_DSP_DIR DIR_SEP;
|
||||
s_user_paths[D_DUMPSSL_IDX] = s_user_paths[D_DUMP_IDX] + DUMP_SSL_DIR DIR_SEP;
|
||||
s_user_paths[D_DUMPDEBUG_IDX] = s_user_paths[D_DUMP_IDX] + DUMP_DEBUG_DIR DIR_SEP;
|
||||
s_user_paths[D_DUMPDEBUG_BRANCHWATCH_IDX] =
|
||||
s_user_paths[D_DUMP_IDX] + DUMP_DEBUG_BRANCHWATCH_DIR DIR_SEP;
|
||||
s_user_paths[F_MEM1DUMP_IDX] = s_user_paths[D_DUMP_IDX] + MEM1_DUMP;
|
||||
s_user_paths[F_MEM2DUMP_IDX] = s_user_paths[D_DUMP_IDX] + MEM2_DUMP;
|
||||
s_user_paths[F_ARAMDUMP_IDX] = s_user_paths[D_DUMP_IDX] + ARAM_DUMP;
|
||||
|
||||
@@ -52,6 +52,8 @@ enum
|
||||
D_DUMPTEXTURES_IDX,
|
||||
D_DUMPDSP_IDX,
|
||||
D_DUMPSSL_IDX,
|
||||
D_DUMPDEBUG_IDX,
|
||||
D_DUMPDEBUG_BRANCHWATCH_IDX,
|
||||
D_LOAD_IDX,
|
||||
D_LOGS_IDX,
|
||||
D_MAILLOGS_IDX,
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "Core/Config/MainSettings.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/Debugger/BranchWatch.h"
|
||||
#include "Core/HLE/HLE.h"
|
||||
#include "Core/HW/DVD/DVDInterface.h"
|
||||
#include "Core/HW/EXI/EXI_DeviceIPL.h"
|
||||
@@ -158,6 +159,11 @@ bool CBoot::RunApploader(Core::System& system, const Core::CPUThreadGuard& guard
|
||||
|
||||
auto& ppc_state = system.GetPPCState();
|
||||
auto& mmu = system.GetMMU();
|
||||
auto& branch_watch = system.GetPowerPC().GetBranchWatch();
|
||||
|
||||
const bool resume_branch_watch = branch_watch.GetRecordingActive();
|
||||
if (system.IsBranchWatchIgnoreApploader())
|
||||
branch_watch.Pause();
|
||||
|
||||
// Call iAppLoaderEntry.
|
||||
DEBUG_LOG_FMT(BOOT, "Call iAppLoaderEntry");
|
||||
@@ -220,6 +226,8 @@ bool CBoot::RunApploader(Core::System& system, const Core::CPUThreadGuard& guard
|
||||
// return
|
||||
ppc_state.pc = ppc_state.gpr[3];
|
||||
|
||||
branch_watch.SetRecordingActive(resume_branch_watch);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,8 @@ add_library(core
|
||||
CoreTiming.h
|
||||
CPUThreadConfigCallback.cpp
|
||||
CPUThreadConfigCallback.h
|
||||
Debugger/BranchWatch.cpp
|
||||
Debugger/BranchWatch.h
|
||||
Debugger/CodeTrace.cpp
|
||||
Debugger/CodeTrace.h
|
||||
Debugger/DebugInterface.h
|
||||
|
||||
@@ -0,0 +1,314 @@
|
||||
// Copyright 2024 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "Core/Debugger/BranchWatch.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "Common/Assert.h"
|
||||
#include "Common/BitField.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/PowerPC/Gekko.h"
|
||||
#include "Core/PowerPC/MMU.h"
|
||||
|
||||
namespace Core
|
||||
{
|
||||
void BranchWatch::Clear(const CPUThreadGuard&)
|
||||
{
|
||||
m_selection.clear();
|
||||
m_collection_vt.clear();
|
||||
m_collection_vf.clear();
|
||||
m_collection_pt.clear();
|
||||
m_collection_pf.clear();
|
||||
m_recording_phase = Phase::Blacklist;
|
||||
m_blacklist_size = 0;
|
||||
}
|
||||
|
||||
// This is a bitfield aggregate of metadata required to reconstruct a BranchWatch's Collections and
|
||||
// Selection from a text file (a snapshot). For maximum forward compatibility, should that ever be
|
||||
// required, the StorageType is an unsigned long long instead of something more reasonable like an
|
||||
// unsigned int or u8. This is because the snapshot text file format contains no version info.
|
||||
union USnapshotMetadata
|
||||
{
|
||||
using Inspection = BranchWatch::SelectionInspection;
|
||||
using StorageType = unsigned long long;
|
||||
|
||||
static_assert(Inspection::EndOfEnumeration == Inspection{(1u << 3) + 1});
|
||||
|
||||
StorageType hex;
|
||||
|
||||
BitField<0, 1, bool, StorageType> is_virtual;
|
||||
BitField<1, 1, bool, StorageType> condition;
|
||||
BitField<2, 1, bool, StorageType> is_selected;
|
||||
BitField<3, 4, Inspection, StorageType> inspection;
|
||||
|
||||
USnapshotMetadata() : hex(0) {}
|
||||
explicit USnapshotMetadata(bool is_virtual_, bool condition_, bool is_selected_,
|
||||
Inspection inspection_)
|
||||
: USnapshotMetadata()
|
||||
{
|
||||
is_virtual = is_virtual_;
|
||||
condition = condition_;
|
||||
is_selected = is_selected_;
|
||||
inspection = inspection_;
|
||||
}
|
||||
};
|
||||
|
||||
void BranchWatch::Save(const CPUThreadGuard& guard, std::FILE* file) const
|
||||
{
|
||||
if (!CanSave())
|
||||
{
|
||||
ASSERT_MSG(CORE, false, "BranchWatch can not be saved.");
|
||||
return;
|
||||
}
|
||||
if (file == nullptr)
|
||||
return;
|
||||
|
||||
const auto routine = [&](const Collection& collection, bool is_virtual, bool condition) {
|
||||
for (const Collection::value_type& kv : collection)
|
||||
{
|
||||
const auto iter = std::find_if(
|
||||
m_selection.begin(), m_selection.end(),
|
||||
[&](const Selection::value_type& value) { return value.collection_ptr == &kv; });
|
||||
fmt::println(file, "{:08x} {:08x} {:08x} {} {} {:x}", kv.first.origin_addr,
|
||||
kv.first.destin_addr, kv.first.original_inst.hex, kv.second.total_hits,
|
||||
kv.second.hits_snapshot,
|
||||
iter == m_selection.end() ?
|
||||
USnapshotMetadata(is_virtual, condition, false, {}).hex :
|
||||
USnapshotMetadata(is_virtual, condition, true, iter->inspection).hex);
|
||||
}
|
||||
};
|
||||
routine(m_collection_vt, true, true);
|
||||
routine(m_collection_pt, false, true);
|
||||
routine(m_collection_vf, true, false);
|
||||
routine(m_collection_pf, false, false);
|
||||
}
|
||||
|
||||
void BranchWatch::Load(const CPUThreadGuard& guard, std::FILE* file)
|
||||
{
|
||||
if (file == nullptr)
|
||||
return;
|
||||
|
||||
Clear(guard);
|
||||
|
||||
u32 origin_addr, destin_addr, inst_hex;
|
||||
std::size_t total_hits, hits_snapshot;
|
||||
USnapshotMetadata snapshot_metadata = {};
|
||||
while (std::fscanf(file, "%x %x %x %zu %zu %llx", &origin_addr, &destin_addr, &inst_hex,
|
||||
&total_hits, &hits_snapshot, &snapshot_metadata.hex) == 6)
|
||||
{
|
||||
const bool is_virtual = snapshot_metadata.is_virtual;
|
||||
const bool condition = snapshot_metadata.condition;
|
||||
|
||||
const auto [kv_iter, emplace_success] =
|
||||
GetCollection(is_virtual, condition)
|
||||
.try_emplace({{origin_addr, destin_addr}, inst_hex},
|
||||
BranchWatchCollectionValue{total_hits, hits_snapshot});
|
||||
|
||||
if (!emplace_success)
|
||||
continue;
|
||||
|
||||
if (snapshot_metadata.is_selected)
|
||||
{
|
||||
// TODO C++20: Parenthesized initialization of aggregates has bad compiler support.
|
||||
m_selection.emplace_back(BranchWatchSelectionValueType{&*kv_iter, is_virtual, condition,
|
||||
snapshot_metadata.inspection});
|
||||
}
|
||||
else if (hits_snapshot != 0)
|
||||
{
|
||||
++m_blacklist_size; // This will be very wrong when not in Blacklist mode. That's ok.
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_selection.empty())
|
||||
m_recording_phase = Phase::Reduction;
|
||||
}
|
||||
|
||||
void BranchWatch::IsolateHasExecuted(const CPUThreadGuard&)
|
||||
{
|
||||
switch (m_recording_phase)
|
||||
{
|
||||
case Phase::Blacklist:
|
||||
{
|
||||
m_selection.reserve(GetCollectionSize() - m_blacklist_size);
|
||||
const auto routine = [&](Collection& collection, bool is_virtual, bool condition) {
|
||||
for (Collection::value_type& kv : collection)
|
||||
{
|
||||
if (kv.second.hits_snapshot == 0)
|
||||
{
|
||||
// TODO C++20: Parenthesized initialization of aggregates has bad compiler support.
|
||||
m_selection.emplace_back(
|
||||
BranchWatchSelectionValueType{&kv, is_virtual, condition, SelectionInspection{}});
|
||||
kv.second.hits_snapshot = kv.second.total_hits;
|
||||
}
|
||||
}
|
||||
};
|
||||
routine(m_collection_vt, true, true);
|
||||
routine(m_collection_vf, true, false);
|
||||
routine(m_collection_pt, false, true);
|
||||
routine(m_collection_pf, false, false);
|
||||
m_recording_phase = Phase::Reduction;
|
||||
return;
|
||||
}
|
||||
case Phase::Reduction:
|
||||
std::erase_if(m_selection, [](const Selection::value_type& value) -> bool {
|
||||
Collection::value_type* const kv = value.collection_ptr;
|
||||
if (kv->second.total_hits == kv->second.hits_snapshot)
|
||||
return true;
|
||||
kv->second.hits_snapshot = kv->second.total_hits;
|
||||
return false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void BranchWatch::IsolateNotExecuted(const CPUThreadGuard&)
|
||||
{
|
||||
switch (m_recording_phase)
|
||||
{
|
||||
case Phase::Blacklist:
|
||||
{
|
||||
const auto routine = [&](Collection& collection) {
|
||||
for (Collection::value_type& kv : collection)
|
||||
kv.second.hits_snapshot = kv.second.total_hits;
|
||||
};
|
||||
routine(m_collection_vt);
|
||||
routine(m_collection_vf);
|
||||
routine(m_collection_pt);
|
||||
routine(m_collection_pf);
|
||||
m_blacklist_size = GetCollectionSize();
|
||||
return;
|
||||
}
|
||||
case Phase::Reduction:
|
||||
std::erase_if(m_selection, [](const Selection::value_type& value) -> bool {
|
||||
Collection::value_type* const kv = value.collection_ptr;
|
||||
if (kv->second.total_hits != kv->second.hits_snapshot)
|
||||
return true;
|
||||
kv->second.hits_snapshot = kv->second.total_hits;
|
||||
return false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void BranchWatch::IsolateWasOverwritten(const CPUThreadGuard& guard)
|
||||
{
|
||||
if (Core::GetState() == Core::State::Uninitialized)
|
||||
{
|
||||
ASSERT_MSG(CORE, false, "Core is uninitialized.");
|
||||
return;
|
||||
}
|
||||
switch (m_recording_phase)
|
||||
{
|
||||
case Phase::Blacklist:
|
||||
{
|
||||
// This is a dirty hack of the assumptions that make the blacklist phase work. If the
|
||||
// hits_snapshot is non-zero while in the blacklist phase, that means it has been marked
|
||||
// for exclusion from the transition to the reduction phase.
|
||||
const auto routine = [&](Collection& collection, PowerPC::RequestedAddressSpace address_space) {
|
||||
for (Collection::value_type& kv : collection)
|
||||
{
|
||||
if (kv.second.hits_snapshot == 0)
|
||||
{
|
||||
const std::optional read_result =
|
||||
PowerPC::MMU::HostTryReadInstruction(guard, kv.first.origin_addr, address_space);
|
||||
if (!read_result.has_value())
|
||||
continue;
|
||||
if (kv.first.original_inst.hex == read_result->value)
|
||||
kv.second.hits_snapshot = ++m_blacklist_size; // Any non-zero number will work.
|
||||
}
|
||||
}
|
||||
};
|
||||
routine(m_collection_vt, PowerPC::RequestedAddressSpace::Virtual);
|
||||
routine(m_collection_vf, PowerPC::RequestedAddressSpace::Virtual);
|
||||
routine(m_collection_pt, PowerPC::RequestedAddressSpace::Physical);
|
||||
routine(m_collection_pf, PowerPC::RequestedAddressSpace::Physical);
|
||||
return;
|
||||
}
|
||||
case Phase::Reduction:
|
||||
std::erase_if(m_selection, [&guard](const Selection::value_type& value) -> bool {
|
||||
const std::optional read_result = PowerPC::MMU::HostTryReadInstruction(
|
||||
guard, value.collection_ptr->first.origin_addr,
|
||||
value.is_virtual ? PowerPC::RequestedAddressSpace::Virtual :
|
||||
PowerPC::RequestedAddressSpace::Physical);
|
||||
if (!read_result.has_value())
|
||||
return false;
|
||||
return value.collection_ptr->first.original_inst.hex == read_result->value;
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void BranchWatch::IsolateNotOverwritten(const CPUThreadGuard& guard)
|
||||
{
|
||||
if (Core::GetState() == Core::State::Uninitialized)
|
||||
{
|
||||
ASSERT_MSG(CORE, false, "Core is uninitialized.");
|
||||
return;
|
||||
}
|
||||
switch (m_recording_phase)
|
||||
{
|
||||
case Phase::Blacklist:
|
||||
{
|
||||
// Same dirty hack with != rather than ==, see above for details
|
||||
const auto routine = [&](Collection& collection, PowerPC::RequestedAddressSpace address_space) {
|
||||
for (Collection::value_type& kv : collection)
|
||||
if (kv.second.hits_snapshot == 0)
|
||||
{
|
||||
const std::optional read_result =
|
||||
PowerPC::MMU::HostTryReadInstruction(guard, kv.first.origin_addr, address_space);
|
||||
if (!read_result.has_value())
|
||||
continue;
|
||||
if (kv.first.original_inst.hex != read_result->value)
|
||||
kv.second.hits_snapshot = ++m_blacklist_size; // Any non-zero number will work.
|
||||
}
|
||||
};
|
||||
routine(m_collection_vt, PowerPC::RequestedAddressSpace::Virtual);
|
||||
routine(m_collection_vf, PowerPC::RequestedAddressSpace::Virtual);
|
||||
routine(m_collection_pt, PowerPC::RequestedAddressSpace::Physical);
|
||||
routine(m_collection_pf, PowerPC::RequestedAddressSpace::Physical);
|
||||
return;
|
||||
}
|
||||
case Phase::Reduction:
|
||||
std::erase_if(m_selection, [&guard](const Selection::value_type& value) -> bool {
|
||||
const std::optional read_result = PowerPC::MMU::HostTryReadInstruction(
|
||||
guard, value.collection_ptr->first.origin_addr,
|
||||
value.is_virtual ? PowerPC::RequestedAddressSpace::Virtual :
|
||||
PowerPC::RequestedAddressSpace::Physical);
|
||||
if (!read_result.has_value())
|
||||
return false;
|
||||
return value.collection_ptr->first.original_inst.hex != read_result->value;
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void BranchWatch::UpdateHitsSnapshot()
|
||||
{
|
||||
switch (m_recording_phase)
|
||||
{
|
||||
case Phase::Reduction:
|
||||
for (Selection::value_type& value : m_selection)
|
||||
value.collection_ptr->second.hits_snapshot = value.collection_ptr->second.total_hits;
|
||||
return;
|
||||
case Phase::Blacklist:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void BranchWatch::ClearSelectionInspection()
|
||||
{
|
||||
std::for_each(m_selection.begin(), m_selection.end(),
|
||||
[](Selection::value_type& value) { value.inspection = {}; });
|
||||
}
|
||||
|
||||
void BranchWatch::SetSelectedInspected(std::size_t idx, SelectionInspection inspection)
|
||||
{
|
||||
m_selection[idx].inspection |= inspection;
|
||||
}
|
||||
} // namespace Core
|
||||
@@ -0,0 +1,278 @@
|
||||
// Copyright 2024 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <functional>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/BitUtils.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/EnumUtils.h"
|
||||
#include "Core/PowerPC/Gekko.h"
|
||||
|
||||
namespace Core
|
||||
{
|
||||
class CPUThreadGuard;
|
||||
}
|
||||
|
||||
namespace Core
|
||||
{
|
||||
struct FakeBranchWatchCollectionKey
|
||||
{
|
||||
u32 origin_addr;
|
||||
u32 destin_addr;
|
||||
|
||||
// TODO C++20: constexpr w/ std::bit_cast
|
||||
inline operator u64() const { return Common::BitCast<u64>(*this); }
|
||||
};
|
||||
struct BranchWatchCollectionKey : FakeBranchWatchCollectionKey
|
||||
{
|
||||
UGeckoInstruction original_inst;
|
||||
};
|
||||
struct BranchWatchCollectionValue
|
||||
{
|
||||
std::size_t total_hits = 0;
|
||||
std::size_t hits_snapshot = 0;
|
||||
};
|
||||
} // namespace Core
|
||||
|
||||
template <>
|
||||
struct std::hash<Core::BranchWatchCollectionKey>
|
||||
{
|
||||
std::size_t operator()(const Core::BranchWatchCollectionKey& s) const noexcept
|
||||
{
|
||||
return std::hash<u64>{}(static_cast<const Core::FakeBranchWatchCollectionKey&>(s));
|
||||
}
|
||||
};
|
||||
|
||||
namespace Core
|
||||
{
|
||||
inline bool operator==(const BranchWatchCollectionKey& lhs,
|
||||
const BranchWatchCollectionKey& rhs) noexcept
|
||||
{
|
||||
const std::hash<BranchWatchCollectionKey> hash;
|
||||
return hash(lhs) == hash(rhs) && lhs.original_inst.hex == rhs.original_inst.hex;
|
||||
}
|
||||
|
||||
enum class BranchWatchSelectionInspection : u8
|
||||
{
|
||||
SetOriginNOP = 1u << 0,
|
||||
SetDestinBLR = 1u << 1,
|
||||
SetOriginSymbolBLR = 1u << 2,
|
||||
SetDestinSymbolBLR = 1u << 3,
|
||||
EndOfEnumeration,
|
||||
};
|
||||
|
||||
constexpr BranchWatchSelectionInspection operator|(BranchWatchSelectionInspection lhs,
|
||||
BranchWatchSelectionInspection rhs)
|
||||
{
|
||||
return static_cast<BranchWatchSelectionInspection>(Common::ToUnderlying(lhs) |
|
||||
Common::ToUnderlying(rhs));
|
||||
}
|
||||
|
||||
constexpr BranchWatchSelectionInspection operator&(BranchWatchSelectionInspection lhs,
|
||||
BranchWatchSelectionInspection rhs)
|
||||
{
|
||||
return static_cast<BranchWatchSelectionInspection>(Common::ToUnderlying(lhs) &
|
||||
Common::ToUnderlying(rhs));
|
||||
}
|
||||
|
||||
constexpr BranchWatchSelectionInspection& operator|=(BranchWatchSelectionInspection& self,
|
||||
BranchWatchSelectionInspection other)
|
||||
{
|
||||
return self = self | other;
|
||||
}
|
||||
|
||||
using BranchWatchCollection =
|
||||
std::unordered_map<BranchWatchCollectionKey, BranchWatchCollectionValue>;
|
||||
|
||||
struct BranchWatchSelectionValueType
|
||||
{
|
||||
using Inspection = BranchWatchSelectionInspection;
|
||||
|
||||
BranchWatchCollection::value_type* collection_ptr;
|
||||
bool is_virtual;
|
||||
bool condition;
|
||||
// This is moreso a GUI thing, but it works best in the Core code for multiple reasons.
|
||||
Inspection inspection;
|
||||
};
|
||||
|
||||
using BranchWatchSelection = std::vector<BranchWatchSelectionValueType>;
|
||||
|
||||
enum class BranchWatchPhase : bool
|
||||
{
|
||||
Blacklist,
|
||||
Reduction,
|
||||
};
|
||||
|
||||
class BranchWatch final // Class is final to enforce the safety of GetOffsetOfRecordingActive().
|
||||
{
|
||||
public:
|
||||
using Collection = BranchWatchCollection;
|
||||
using Selection = BranchWatchSelection;
|
||||
using Phase = BranchWatchPhase;
|
||||
using SelectionInspection = BranchWatchSelectionInspection;
|
||||
|
||||
bool GetRecordingActive() const { return m_recording_active; }
|
||||
void SetRecordingActive(bool active) { m_recording_active = active; }
|
||||
void Start() { SetRecordingActive(true); }
|
||||
void Pause() { SetRecordingActive(false); }
|
||||
void Clear(const CPUThreadGuard& guard);
|
||||
|
||||
void Save(const CPUThreadGuard& guard, std::FILE* file) const;
|
||||
void Load(const CPUThreadGuard& guard, std::FILE* file);
|
||||
|
||||
void IsolateHasExecuted(const CPUThreadGuard& guard);
|
||||
void IsolateNotExecuted(const CPUThreadGuard& guard);
|
||||
void IsolateWasOverwritten(const CPUThreadGuard& guard);
|
||||
void IsolateNotOverwritten(const CPUThreadGuard& guard);
|
||||
void UpdateHitsSnapshot();
|
||||
void ClearSelectionInspection();
|
||||
void SetSelectedInspected(std::size_t idx, SelectionInspection inspection);
|
||||
|
||||
Selection& GetSelection() { return m_selection; }
|
||||
const Selection& GetSelection() const { return m_selection; }
|
||||
|
||||
std::size_t GetCollectionSize() const
|
||||
{
|
||||
return m_collection_vt.size() + m_collection_vf.size() + m_collection_pt.size() +
|
||||
m_collection_pf.size();
|
||||
}
|
||||
std::size_t GetBlacklistSize() const { return m_blacklist_size; }
|
||||
Phase GetRecordingPhase() const { return m_recording_phase; };
|
||||
|
||||
// An empty selection in reduction mode can't be reconstructed when loading from a file.
|
||||
bool CanSave() const { return !(m_recording_phase == Phase::Reduction && m_selection.empty()); }
|
||||
|
||||
// All Hit member functions are for the CPUThread only. The static ones are static to remain
|
||||
// compatible with the JITs' ABI_CallFunction function, which doesn't support non-static member
|
||||
// functions. HitXX_fk are optimized for when origin and destination can be passed in one register
|
||||
// easily as a Core::FakeBranchWatchCollectionKey (abbreviated as "fk"). HitXX_fk_n are the same,
|
||||
// but also increment the total_hits by N (see dcbx JIT code).
|
||||
static void HitVirtualTrue_fk(BranchWatch* branch_watch, u64 fake_key, u32 inst)
|
||||
{
|
||||
branch_watch->m_collection_vt[{Common::BitCast<FakeBranchWatchCollectionKey>(fake_key), inst}]
|
||||
.total_hits += 1;
|
||||
}
|
||||
|
||||
static void HitPhysicalTrue_fk(BranchWatch* branch_watch, u64 fake_key, u32 inst)
|
||||
{
|
||||
branch_watch->m_collection_pt[{Common::BitCast<FakeBranchWatchCollectionKey>(fake_key), inst}]
|
||||
.total_hits += 1;
|
||||
}
|
||||
|
||||
static void HitVirtualFalse_fk(BranchWatch* branch_watch, u64 fake_key, u32 inst)
|
||||
{
|
||||
branch_watch->m_collection_vf[{Common::BitCast<FakeBranchWatchCollectionKey>(fake_key), inst}]
|
||||
.total_hits += 1;
|
||||
}
|
||||
|
||||
static void HitPhysicalFalse_fk(BranchWatch* branch_watch, u64 fake_key, u32 inst)
|
||||
{
|
||||
branch_watch->m_collection_pf[{Common::BitCast<FakeBranchWatchCollectionKey>(fake_key), inst}]
|
||||
.total_hits += 1;
|
||||
}
|
||||
|
||||
static void HitVirtualTrue_fk_n(BranchWatch* branch_watch, u64 fake_key, u32 inst, u32 n)
|
||||
{
|
||||
branch_watch->m_collection_vt[{Common::BitCast<FakeBranchWatchCollectionKey>(fake_key), inst}]
|
||||
.total_hits += n;
|
||||
}
|
||||
|
||||
static void HitPhysicalTrue_fk_n(BranchWatch* branch_watch, u64 fake_key, u32 inst, u32 n)
|
||||
{
|
||||
branch_watch->m_collection_pt[{Common::BitCast<FakeBranchWatchCollectionKey>(fake_key), inst}]
|
||||
.total_hits += n;
|
||||
}
|
||||
|
||||
// HitVirtualFalse_fk_n and HitPhysicalFalse_fk_n are never used, so they are omitted here.
|
||||
|
||||
static void HitVirtualTrue(BranchWatch* branch_watch, u32 origin, u32 destination, u32 inst)
|
||||
{
|
||||
HitVirtualTrue_fk(branch_watch, FakeBranchWatchCollectionKey{origin, destination}, inst);
|
||||
}
|
||||
|
||||
static void HitPhysicalTrue(BranchWatch* branch_watch, u32 origin, u32 destination, u32 inst)
|
||||
{
|
||||
HitPhysicalTrue_fk(branch_watch, FakeBranchWatchCollectionKey{origin, destination}, inst);
|
||||
}
|
||||
|
||||
static void HitVirtualFalse(BranchWatch* branch_watch, u32 origin, u32 destination, u32 inst)
|
||||
{
|
||||
HitVirtualFalse_fk(branch_watch, FakeBranchWatchCollectionKey{origin, destination}, inst);
|
||||
}
|
||||
|
||||
static void HitPhysicalFalse(BranchWatch* branch_watch, u32 origin, u32 destination, u32 inst)
|
||||
{
|
||||
HitPhysicalFalse_fk(branch_watch, FakeBranchWatchCollectionKey{origin, destination}, inst);
|
||||
}
|
||||
|
||||
void HitTrue(u32 origin, u32 destination, UGeckoInstruction inst, bool translate)
|
||||
{
|
||||
if (translate)
|
||||
HitVirtualTrue(this, origin, destination, inst.hex);
|
||||
else
|
||||
HitPhysicalTrue(this, origin, destination, inst.hex);
|
||||
}
|
||||
|
||||
void HitFalse(u32 origin, u32 destination, UGeckoInstruction inst, bool translate)
|
||||
{
|
||||
if (translate)
|
||||
HitVirtualFalse(this, origin, destination, inst.hex);
|
||||
else
|
||||
HitPhysicalFalse(this, origin, destination, inst.hex);
|
||||
}
|
||||
|
||||
// The JIT needs this value, but doesn't need to be a full-on friend.
|
||||
static constexpr int GetOffsetOfRecordingActive()
|
||||
{
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Winvalid-offsetof"
|
||||
#endif
|
||||
return offsetof(BranchWatch, m_recording_active);
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
Collection& GetCollectionV(bool condition)
|
||||
{
|
||||
if (condition)
|
||||
return m_collection_vt;
|
||||
return m_collection_vf;
|
||||
}
|
||||
|
||||
Collection& GetCollectionP(bool condition)
|
||||
{
|
||||
if (condition)
|
||||
return m_collection_pt;
|
||||
return m_collection_pf;
|
||||
}
|
||||
|
||||
Collection& GetCollection(bool is_virtual, bool condition)
|
||||
{
|
||||
if (is_virtual)
|
||||
return GetCollectionV(condition);
|
||||
return GetCollectionP(condition);
|
||||
}
|
||||
|
||||
std::size_t m_blacklist_size = 0;
|
||||
Phase m_recording_phase = Phase::Blacklist;
|
||||
bool m_recording_active = false;
|
||||
Collection m_collection_vt; // virtual address space | true path
|
||||
Collection m_collection_vf; // virtual address space | false path
|
||||
Collection m_collection_pt; // physical address space | true path
|
||||
Collection m_collection_pf; // physical address space | false path
|
||||
Selection m_selection;
|
||||
};
|
||||
|
||||
#if _M_X86_64
|
||||
static_assert(BranchWatch::GetOffsetOfRecordingActive() < 0x80); // Makes JIT code smaller.
|
||||
#endif
|
||||
} // namespace Core
|
||||
@@ -64,8 +64,9 @@ void Interpreter::UpdatePC()
|
||||
m_ppc_state.pc = m_ppc_state.npc;
|
||||
}
|
||||
|
||||
Interpreter::Interpreter(Core::System& system, PowerPC::PowerPCState& ppc_state, PowerPC::MMU& mmu)
|
||||
: m_system(system), m_ppc_state(ppc_state), m_mmu(mmu)
|
||||
Interpreter::Interpreter(Core::System& system, PowerPC::PowerPCState& ppc_state, PowerPC::MMU& mmu,
|
||||
Core::BranchWatch& branch_watch)
|
||||
: m_system(system), m_ppc_state(ppc_state), m_mmu(mmu), m_branch_watch(branch_watch)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -11,8 +11,9 @@
|
||||
|
||||
namespace Core
|
||||
{
|
||||
class BranchWatch;
|
||||
class System;
|
||||
}
|
||||
} // namespace Core
|
||||
namespace PowerPC
|
||||
{
|
||||
class MMU;
|
||||
@@ -22,7 +23,8 @@ struct PowerPCState;
|
||||
class Interpreter : public CPUCoreBase
|
||||
{
|
||||
public:
|
||||
Interpreter(Core::System& system, PowerPC::PowerPCState& ppc_state, PowerPC::MMU& mmu);
|
||||
Interpreter(Core::System& system, PowerPC::PowerPCState& ppc_state, PowerPC::MMU& mmu,
|
||||
Core::BranchWatch& branch_watch);
|
||||
Interpreter(const Interpreter&) = delete;
|
||||
Interpreter(Interpreter&&) = delete;
|
||||
Interpreter& operator=(const Interpreter&) = delete;
|
||||
@@ -314,6 +316,7 @@ private:
|
||||
Core::System& m_system;
|
||||
PowerPC::PowerPCState& m_ppc_state;
|
||||
PowerPC::MMU& m_mmu;
|
||||
Core::BranchWatch& m_branch_watch;
|
||||
|
||||
UGeckoInstruction m_prev_inst{};
|
||||
u32 m_last_pc = 0;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/Debugger/BranchWatch.h"
|
||||
#include "Core/HLE/HLE.h"
|
||||
#include "Core/PowerPC/Interpreter/ExceptionUtils.h"
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
@@ -19,12 +20,13 @@ void Interpreter::bx(Interpreter& interpreter, UGeckoInstruction inst)
|
||||
if (inst.LK)
|
||||
LR(ppc_state) = ppc_state.pc + 4;
|
||||
|
||||
const auto address = u32(SignExt26(inst.LI << 2));
|
||||
u32 destination_addr = u32(SignExt26(inst.LI << 2));
|
||||
if (!inst.AA)
|
||||
destination_addr += ppc_state.pc;
|
||||
ppc_state.npc = destination_addr;
|
||||
|
||||
if (inst.AA)
|
||||
ppc_state.npc = address;
|
||||
else
|
||||
ppc_state.npc = ppc_state.pc + address;
|
||||
if (auto& branch_watch = interpreter.m_branch_watch; branch_watch.GetRecordingActive())
|
||||
branch_watch.HitTrue(ppc_state.pc, destination_addr, inst, ppc_state.msr.IR);
|
||||
|
||||
interpreter.m_end_block = true;
|
||||
}
|
||||
@@ -33,6 +35,7 @@ void Interpreter::bx(Interpreter& interpreter, UGeckoInstruction inst)
|
||||
void Interpreter::bcx(Interpreter& interpreter, UGeckoInstruction inst)
|
||||
{
|
||||
auto& ppc_state = interpreter.m_ppc_state;
|
||||
auto& branch_watch = interpreter.m_branch_watch;
|
||||
|
||||
if ((inst.BO & BO_DONT_DECREMENT_FLAG) == 0)
|
||||
CTR(ppc_state)--;
|
||||
@@ -49,12 +52,17 @@ void Interpreter::bcx(Interpreter& interpreter, UGeckoInstruction inst)
|
||||
if (inst.LK)
|
||||
LR(ppc_state) = ppc_state.pc + 4;
|
||||
|
||||
const auto address = u32(SignExt16(s16(inst.BD << 2)));
|
||||
u32 destination_addr = u32(SignExt16(s16(inst.BD << 2)));
|
||||
if (!inst.AA)
|
||||
destination_addr += ppc_state.pc;
|
||||
ppc_state.npc = destination_addr;
|
||||
|
||||
if (inst.AA)
|
||||
ppc_state.npc = address;
|
||||
else
|
||||
ppc_state.npc = ppc_state.pc + address;
|
||||
if (branch_watch.GetRecordingActive())
|
||||
branch_watch.HitTrue(ppc_state.pc, destination_addr, inst, ppc_state.msr.IR);
|
||||
}
|
||||
else if (branch_watch.GetRecordingActive())
|
||||
{
|
||||
branch_watch.HitFalse(ppc_state.pc, ppc_state.pc + 4, inst, ppc_state.msr.IR);
|
||||
}
|
||||
|
||||
interpreter.m_end_block = true;
|
||||
@@ -63,6 +71,7 @@ void Interpreter::bcx(Interpreter& interpreter, UGeckoInstruction inst)
|
||||
void Interpreter::bcctrx(Interpreter& interpreter, UGeckoInstruction inst)
|
||||
{
|
||||
auto& ppc_state = interpreter.m_ppc_state;
|
||||
auto& branch_watch = interpreter.m_branch_watch;
|
||||
|
||||
DEBUG_ASSERT_MSG(POWERPC, (inst.BO_2 & BO_DONT_DECREMENT_FLAG) != 0,
|
||||
"bcctrx with decrement and test CTR option is invalid!");
|
||||
@@ -72,9 +81,17 @@ void Interpreter::bcctrx(Interpreter& interpreter, UGeckoInstruction inst)
|
||||
|
||||
if (condition != 0)
|
||||
{
|
||||
ppc_state.npc = CTR(ppc_state) & (~3);
|
||||
const u32 destination_addr = CTR(ppc_state) & (~3);
|
||||
ppc_state.npc = destination_addr;
|
||||
if (inst.LK_3)
|
||||
LR(ppc_state) = ppc_state.pc + 4;
|
||||
|
||||
if (branch_watch.GetRecordingActive())
|
||||
branch_watch.HitTrue(ppc_state.pc, destination_addr, inst, ppc_state.msr.IR);
|
||||
}
|
||||
else if (branch_watch.GetRecordingActive())
|
||||
{
|
||||
branch_watch.HitFalse(ppc_state.pc, ppc_state.pc + 4, inst, ppc_state.msr.IR);
|
||||
}
|
||||
|
||||
interpreter.m_end_block = true;
|
||||
@@ -83,6 +100,7 @@ void Interpreter::bcctrx(Interpreter& interpreter, UGeckoInstruction inst)
|
||||
void Interpreter::bclrx(Interpreter& interpreter, UGeckoInstruction inst)
|
||||
{
|
||||
auto& ppc_state = interpreter.m_ppc_state;
|
||||
auto& branch_watch = interpreter.m_branch_watch;
|
||||
|
||||
if ((inst.BO_2 & BO_DONT_DECREMENT_FLAG) == 0)
|
||||
CTR(ppc_state)--;
|
||||
@@ -93,9 +111,17 @@ void Interpreter::bclrx(Interpreter& interpreter, UGeckoInstruction inst)
|
||||
|
||||
if ((counter & condition) != 0)
|
||||
{
|
||||
ppc_state.npc = LR(ppc_state) & (~3);
|
||||
const u32 destination_addr = LR(ppc_state) & (~3);
|
||||
ppc_state.npc = destination_addr;
|
||||
if (inst.LK_3)
|
||||
LR(ppc_state) = ppc_state.pc + 4;
|
||||
|
||||
if (branch_watch.GetRecordingActive())
|
||||
branch_watch.HitTrue(ppc_state.pc, destination_addr, inst, ppc_state.msr.IR);
|
||||
}
|
||||
else if (branch_watch.GetRecordingActive())
|
||||
{
|
||||
branch_watch.HitFalse(ppc_state.pc, ppc_state.pc + 4, inst, ppc_state.msr.IR);
|
||||
}
|
||||
|
||||
interpreter.m_end_block = true;
|
||||
|
||||
@@ -1041,7 +1041,18 @@ bool Jit64::DoJit(u32 em_address, JitBlock* b, u32 nextPC)
|
||||
if (HandleFunctionHooking(op.address))
|
||||
break;
|
||||
|
||||
if (!op.skip)
|
||||
if (op.skip)
|
||||
{
|
||||
if (IsDebuggingEnabled())
|
||||
{
|
||||
// The only thing that currently sets op.skip is the BLR following optimization.
|
||||
// If any non-branch instruction starts setting that too, this will need to be changed.
|
||||
ASSERT(op.inst.hex == 0x4e800020);
|
||||
WriteBranchWatch<true>(op.address, op.branchTo, op.inst, RSCRATCH, RSCRATCH2,
|
||||
CallerSavedRegistersInUse());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((opinfo->flags & FL_USE_FPU) && !js.firstFPInstructionFound)
|
||||
{
|
||||
|
||||
@@ -98,6 +98,12 @@ public:
|
||||
void WriteExternalExceptionExit();
|
||||
void WriteRfiExitDestInRSCRATCH();
|
||||
void WriteIdleExit(u32 destination);
|
||||
template <bool condition>
|
||||
void WriteBranchWatch(u32 origin, u32 destination, UGeckoInstruction inst, Gen::X64Reg reg_a,
|
||||
Gen::X64Reg reg_b, BitSet32 caller_save);
|
||||
void WriteBranchWatchDestInRSCRATCH(u32 origin, UGeckoInstruction inst, Gen::X64Reg reg_a,
|
||||
Gen::X64Reg reg_b, BitSet32 caller_save);
|
||||
|
||||
bool Cleanup();
|
||||
|
||||
void GenerateConstantOverflow(bool overflow);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/x64Emitter.h"
|
||||
#include "Core/CoreTiming.h"
|
||||
#include "Core/Debugger/BranchWatch.h"
|
||||
#include "Core/PowerPC/Gekko.h"
|
||||
#include "Core/PowerPC/Jit64/RegCache/JitRegCache.h"
|
||||
#include "Core/PowerPC/Jit64Common/Jit64PowerPCState.h"
|
||||
@@ -66,6 +67,68 @@ void Jit64::rfi(UGeckoInstruction inst)
|
||||
WriteRfiExitDestInRSCRATCH();
|
||||
}
|
||||
|
||||
template <bool condition>
|
||||
void Jit64::WriteBranchWatch(u32 origin, u32 destination, UGeckoInstruction inst, X64Reg reg_a,
|
||||
X64Reg reg_b, BitSet32 caller_save)
|
||||
{
|
||||
MOV(64, R(reg_a), ImmPtr(&m_branch_watch));
|
||||
MOVZX(32, 8, reg_b, MDisp(reg_a, Core::BranchWatch::GetOffsetOfRecordingActive()));
|
||||
TEST(32, R(reg_b), R(reg_b));
|
||||
|
||||
FixupBranch branch_in = J_CC(CC_NZ, Jump::Near);
|
||||
SwitchToFarCode();
|
||||
SetJumpTarget(branch_in);
|
||||
|
||||
ABI_PushRegistersAndAdjustStack(caller_save, 0);
|
||||
// Some call sites have an optimization to use ABI_PARAM1 as a scratch register.
|
||||
if (reg_a != ABI_PARAM1)
|
||||
MOV(64, R(ABI_PARAM1), R(reg_a));
|
||||
MOV(64, R(ABI_PARAM2), Imm64(Core::FakeBranchWatchCollectionKey{origin, destination}));
|
||||
MOV(32, R(ABI_PARAM3), Imm32(inst.hex));
|
||||
ABI_CallFunction(m_ppc_state.msr.IR ? (condition ? &Core::BranchWatch::HitVirtualTrue_fk :
|
||||
&Core::BranchWatch::HitVirtualFalse_fk) :
|
||||
(condition ? &Core::BranchWatch::HitPhysicalTrue_fk :
|
||||
&Core::BranchWatch::HitPhysicalFalse_fk));
|
||||
ABI_PopRegistersAndAdjustStack(caller_save, 0);
|
||||
|
||||
FixupBranch branch_out = J(Jump::Near);
|
||||
SwitchToNearCode();
|
||||
SetJumpTarget(branch_out);
|
||||
}
|
||||
|
||||
template void Jit64::WriteBranchWatch<true>(u32, u32, UGeckoInstruction, X64Reg, X64Reg, BitSet32);
|
||||
template void Jit64::WriteBranchWatch<false>(u32, u32, UGeckoInstruction, X64Reg, X64Reg, BitSet32);
|
||||
|
||||
void Jit64::WriteBranchWatchDestInRSCRATCH(u32 origin, UGeckoInstruction inst, X64Reg reg_a,
|
||||
X64Reg reg_b, BitSet32 caller_save)
|
||||
{
|
||||
MOV(64, R(reg_a), ImmPtr(&m_branch_watch));
|
||||
MOVZX(32, 8, reg_b, MDisp(reg_a, Core::BranchWatch::GetOffsetOfRecordingActive()));
|
||||
TEST(32, R(reg_b), R(reg_b));
|
||||
|
||||
FixupBranch branch_in = J_CC(CC_NZ, Jump::Near);
|
||||
SwitchToFarCode();
|
||||
SetJumpTarget(branch_in);
|
||||
|
||||
// Assert RSCRATCH won't be clobbered before it is moved from.
|
||||
static_assert(ABI_PARAM1 != RSCRATCH);
|
||||
|
||||
ABI_PushRegistersAndAdjustStack(caller_save, 0);
|
||||
// Some call sites have an optimization to use ABI_PARAM1 as a scratch register.
|
||||
if (reg_a != ABI_PARAM1)
|
||||
MOV(64, R(ABI_PARAM1), R(reg_a));
|
||||
MOV(32, R(ABI_PARAM3), R(RSCRATCH));
|
||||
MOV(32, R(ABI_PARAM2), Imm32(origin));
|
||||
MOV(32, R(ABI_PARAM4), Imm32(inst.hex));
|
||||
ABI_CallFunction(m_ppc_state.msr.IR ? &Core::BranchWatch::HitVirtualTrue :
|
||||
&Core::BranchWatch::HitPhysicalTrue);
|
||||
ABI_PopRegistersAndAdjustStack(caller_save, 0);
|
||||
|
||||
FixupBranch branch_out = J(Jump::Near);
|
||||
SwitchToNearCode();
|
||||
SetJumpTarget(branch_out);
|
||||
}
|
||||
|
||||
void Jit64::bx(UGeckoInstruction inst)
|
||||
{
|
||||
INSTRUCTION_START
|
||||
@@ -81,6 +144,11 @@ void Jit64::bx(UGeckoInstruction inst)
|
||||
// Because PPCAnalyst::Flatten() merged the blocks.
|
||||
if (!js.isLastInstruction)
|
||||
{
|
||||
if (IsDebuggingEnabled())
|
||||
{
|
||||
WriteBranchWatch<true>(js.compilerPC, js.op->branchTo, inst, RSCRATCH, RSCRATCH2,
|
||||
CallerSavedRegistersInUse());
|
||||
}
|
||||
if (inst.LK && !js.op->skipLRStack)
|
||||
{
|
||||
// We have to fake the stack as the RET instruction was not
|
||||
@@ -94,6 +162,11 @@ void Jit64::bx(UGeckoInstruction inst)
|
||||
gpr.Flush();
|
||||
fpr.Flush();
|
||||
|
||||
if (IsDebuggingEnabled())
|
||||
{
|
||||
// ABI_PARAM1 is safe to use after a GPR flush for an optimization in this function.
|
||||
WriteBranchWatch<true>(js.compilerPC, js.op->branchTo, inst, ABI_PARAM1, RSCRATCH, {});
|
||||
}
|
||||
#ifdef ACID_TEST
|
||||
if (inst.LK)
|
||||
AND(32, PPCSTATE(cr), Imm32(~(0xFF000000)));
|
||||
@@ -144,6 +217,11 @@ void Jit64::bcx(UGeckoInstruction inst)
|
||||
if (!js.isLastInstruction && (inst.BO & BO_DONT_DECREMENT_FLAG) &&
|
||||
(inst.BO & BO_DONT_CHECK_CONDITION))
|
||||
{
|
||||
if (IsDebuggingEnabled())
|
||||
{
|
||||
WriteBranchWatch<true>(js.compilerPC, js.op->branchTo, inst, RSCRATCH, RSCRATCH2,
|
||||
CallerSavedRegistersInUse());
|
||||
}
|
||||
if (inst.LK && !js.op->skipLRStack)
|
||||
{
|
||||
// We have to fake the stack as the RET instruction was not
|
||||
@@ -160,6 +238,11 @@ void Jit64::bcx(UGeckoInstruction inst)
|
||||
gpr.Flush();
|
||||
fpr.Flush();
|
||||
|
||||
if (IsDebuggingEnabled())
|
||||
{
|
||||
// ABI_PARAM1 is safe to use after a GPR flush for an optimization in this function.
|
||||
WriteBranchWatch<true>(js.compilerPC, js.op->branchTo, inst, ABI_PARAM1, RSCRATCH, {});
|
||||
}
|
||||
if (js.op->branchIsIdleLoop)
|
||||
{
|
||||
WriteIdleExit(js.op->branchTo);
|
||||
@@ -179,8 +262,18 @@ void Jit64::bcx(UGeckoInstruction inst)
|
||||
{
|
||||
gpr.Flush();
|
||||
fpr.Flush();
|
||||
if (IsDebuggingEnabled())
|
||||
{
|
||||
// ABI_PARAM1 is safe to use after a GPR flush for an optimization in this function.
|
||||
WriteBranchWatch<false>(js.compilerPC, js.compilerPC + 4, inst, ABI_PARAM1, RSCRATCH, {});
|
||||
}
|
||||
WriteExit(js.compilerPC + 4);
|
||||
}
|
||||
else if (IsDebuggingEnabled())
|
||||
{
|
||||
WriteBranchWatch<false>(js.compilerPC, js.compilerPC + 4, inst, RSCRATCH, RSCRATCH2,
|
||||
CallerSavedRegistersInUse());
|
||||
}
|
||||
}
|
||||
|
||||
void Jit64::bcctrx(UGeckoInstruction inst)
|
||||
@@ -204,6 +297,12 @@ void Jit64::bcctrx(UGeckoInstruction inst)
|
||||
if (inst.LK_3)
|
||||
MOV(32, PPCSTATE_LR, Imm32(js.compilerPC + 4)); // LR = PC + 4;
|
||||
AND(32, R(RSCRATCH), Imm32(0xFFFFFFFC));
|
||||
if (IsDebuggingEnabled())
|
||||
{
|
||||
// ABI_PARAM1 is safe to use after a GPR flush for an optimization in this function.
|
||||
WriteBranchWatchDestInRSCRATCH(js.compilerPC, inst, ABI_PARAM1, RSCRATCH2,
|
||||
BitSet32{RSCRATCH});
|
||||
}
|
||||
WriteExitDestInRSCRATCH(inst.LK_3, js.compilerPC + 4);
|
||||
}
|
||||
else
|
||||
@@ -226,6 +325,12 @@ void Jit64::bcctrx(UGeckoInstruction inst)
|
||||
RCForkGuard fpr_guard = fpr.Fork();
|
||||
gpr.Flush();
|
||||
fpr.Flush();
|
||||
if (IsDebuggingEnabled())
|
||||
{
|
||||
// ABI_PARAM1 is safe to use after a GPR flush for an optimization in this function.
|
||||
WriteBranchWatchDestInRSCRATCH(js.compilerPC, inst, ABI_PARAM1, RSCRATCH2,
|
||||
BitSet32{RSCRATCH});
|
||||
}
|
||||
WriteExitDestInRSCRATCH(inst.LK_3, js.compilerPC + 4);
|
||||
// Would really like to continue the block here, but it ends. TODO.
|
||||
}
|
||||
@@ -235,8 +340,18 @@ void Jit64::bcctrx(UGeckoInstruction inst)
|
||||
{
|
||||
gpr.Flush();
|
||||
fpr.Flush();
|
||||
if (IsDebuggingEnabled())
|
||||
{
|
||||
// ABI_PARAM1 is safe to use after a GPR flush for an optimization in this function.
|
||||
WriteBranchWatch<false>(js.compilerPC, js.compilerPC + 4, inst, ABI_PARAM1, RSCRATCH, {});
|
||||
}
|
||||
WriteExit(js.compilerPC + 4);
|
||||
}
|
||||
else if (IsDebuggingEnabled())
|
||||
{
|
||||
WriteBranchWatch<false>(js.compilerPC, js.compilerPC + 4, inst, RSCRATCH, RSCRATCH2,
|
||||
CallerSavedRegistersInUse());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,10 +385,8 @@ void Jit64::bclrx(UGeckoInstruction inst)
|
||||
|
||||
MOV(32, R(RSCRATCH), PPCSTATE_LR);
|
||||
// We don't have to do this because WriteBLRExit handles it for us. Specifically, since we only
|
||||
// ever push
|
||||
// divisible-by-four instruction addresses onto the stack, if the return address matches, we're
|
||||
// already
|
||||
// good. If it doesn't match, the mispredicted-BLR code handles the fixup.
|
||||
// ever push divisible-by-four instruction addresses onto the stack, if the return address
|
||||
// matches, we're already good. If it doesn't match, the mispredicted-BLR code handles the fixup.
|
||||
if (!m_enable_blr_optimization)
|
||||
AND(32, R(RSCRATCH), Imm32(0xFFFFFFFC));
|
||||
if (inst.LK)
|
||||
@@ -287,10 +400,21 @@ void Jit64::bclrx(UGeckoInstruction inst)
|
||||
|
||||
if (js.op->branchIsIdleLoop)
|
||||
{
|
||||
if (IsDebuggingEnabled())
|
||||
{
|
||||
// ABI_PARAM1 is safe to use after a GPR flush for an optimization in this function.
|
||||
WriteBranchWatch<true>(js.compilerPC, js.op->branchTo, inst, ABI_PARAM1, RSCRATCH, {});
|
||||
}
|
||||
WriteIdleExit(js.op->branchTo);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (IsDebuggingEnabled())
|
||||
{
|
||||
// ABI_PARAM1 is safe to use after a GPR flush for an optimization in this function.
|
||||
WriteBranchWatchDestInRSCRATCH(js.compilerPC, inst, ABI_PARAM1, RSCRATCH2,
|
||||
BitSet32{RSCRATCH});
|
||||
}
|
||||
WriteBLRExit();
|
||||
}
|
||||
}
|
||||
@@ -304,6 +428,16 @@ void Jit64::bclrx(UGeckoInstruction inst)
|
||||
{
|
||||
gpr.Flush();
|
||||
fpr.Flush();
|
||||
if (IsDebuggingEnabled())
|
||||
{
|
||||
// ABI_PARAM1 is safe to use after a GPR flush for an optimization in this function.
|
||||
WriteBranchWatch<false>(js.compilerPC, js.compilerPC + 4, inst, ABI_PARAM1, RSCRATCH, {});
|
||||
}
|
||||
WriteExit(js.compilerPC + 4);
|
||||
}
|
||||
else if (IsDebuggingEnabled())
|
||||
{
|
||||
WriteBranchWatch<false>(js.compilerPC, js.compilerPC + 4, inst, RSCRATCH, RSCRATCH2,
|
||||
CallerSavedRegistersInUse());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -394,18 +394,25 @@ void Jit64::DoMergedBranch()
|
||||
if (next.LK)
|
||||
MOV(32, PPCSTATE_SPR(SPR_LR), Imm32(nextPC + 4));
|
||||
|
||||
WriteIdleExit(js.op[1].branchTo);
|
||||
const u32 destination = js.op[1].branchTo;
|
||||
if (IsDebuggingEnabled())
|
||||
{
|
||||
// ABI_PARAM1 is safe to use after a GPR flush for an optimization in this function.
|
||||
WriteBranchWatch<true>(nextPC, destination, next, ABI_PARAM1, RSCRATCH, {});
|
||||
}
|
||||
WriteIdleExit(destination);
|
||||
}
|
||||
else if (next.OPCD == 16) // bcx
|
||||
{
|
||||
if (next.LK)
|
||||
MOV(32, PPCSTATE_SPR(SPR_LR), Imm32(nextPC + 4));
|
||||
|
||||
u32 destination;
|
||||
if (next.AA)
|
||||
destination = SignExt16(next.BD << 2);
|
||||
else
|
||||
destination = nextPC + SignExt16(next.BD << 2);
|
||||
const u32 destination = js.op[1].branchTo;
|
||||
if (IsDebuggingEnabled())
|
||||
{
|
||||
// ABI_PARAM1 is safe to use after a GPR flush for an optimization in this function.
|
||||
WriteBranchWatch<true>(nextPC, destination, next, ABI_PARAM1, RSCRATCH, {});
|
||||
}
|
||||
WriteExit(destination, next.LK, nextPC + 4);
|
||||
}
|
||||
else if ((next.OPCD == 19) && (next.SUBOP10 == 528)) // bcctrx
|
||||
@@ -414,6 +421,11 @@ void Jit64::DoMergedBranch()
|
||||
MOV(32, PPCSTATE_SPR(SPR_LR), Imm32(nextPC + 4));
|
||||
MOV(32, R(RSCRATCH), PPCSTATE_SPR(SPR_CTR));
|
||||
AND(32, R(RSCRATCH), Imm32(0xFFFFFFFC));
|
||||
if (IsDebuggingEnabled())
|
||||
{
|
||||
// ABI_PARAM1 is safe to use after a GPR flush for an optimization in this function.
|
||||
WriteBranchWatchDestInRSCRATCH(nextPC, next, ABI_PARAM1, RSCRATCH2, BitSet32{RSCRATCH});
|
||||
}
|
||||
WriteExitDestInRSCRATCH(next.LK, nextPC + 4);
|
||||
}
|
||||
else if ((next.OPCD == 19) && (next.SUBOP10 == 16)) // bclrx
|
||||
@@ -423,6 +435,11 @@ void Jit64::DoMergedBranch()
|
||||
AND(32, R(RSCRATCH), Imm32(0xFFFFFFFC));
|
||||
if (next.LK)
|
||||
MOV(32, PPCSTATE_SPR(SPR_LR), Imm32(nextPC + 4));
|
||||
if (IsDebuggingEnabled())
|
||||
{
|
||||
// ABI_PARAM1 is safe to use after a GPR flush for an optimization in this function.
|
||||
WriteBranchWatchDestInRSCRATCH(nextPC, next, ABI_PARAM1, RSCRATCH2, BitSet32{RSCRATCH});
|
||||
}
|
||||
WriteBLRExit();
|
||||
}
|
||||
else
|
||||
@@ -480,8 +497,18 @@ void Jit64::DoMergedBranchCondition()
|
||||
{
|
||||
gpr.Flush();
|
||||
fpr.Flush();
|
||||
if (IsDebuggingEnabled())
|
||||
{
|
||||
// ABI_PARAM1 is safe to use after a GPR flush for an optimization in this function.
|
||||
WriteBranchWatch<false>(nextPC, nextPC + 4, next, ABI_PARAM1, RSCRATCH, {});
|
||||
}
|
||||
WriteExit(nextPC + 4);
|
||||
}
|
||||
else if (IsDebuggingEnabled())
|
||||
{
|
||||
WriteBranchWatch<false>(nextPC, nextPC + 4, next, RSCRATCH, RSCRATCH2,
|
||||
CallerSavedRegistersInUse());
|
||||
}
|
||||
}
|
||||
|
||||
void Jit64::DoMergedBranchImmediate(s64 val)
|
||||
@@ -515,8 +542,18 @@ void Jit64::DoMergedBranchImmediate(s64 val)
|
||||
{
|
||||
gpr.Flush();
|
||||
fpr.Flush();
|
||||
if (IsDebuggingEnabled())
|
||||
{
|
||||
// ABI_PARAM1 is safe to use after a GPR flush for an optimization in this function.
|
||||
WriteBranchWatch<false>(nextPC, nextPC + 4, next, ABI_PARAM1, RSCRATCH, {});
|
||||
}
|
||||
WriteExit(nextPC + 4);
|
||||
}
|
||||
else if (IsDebuggingEnabled())
|
||||
{
|
||||
WriteBranchWatch<false>(nextPC, nextPC + 4, next, RSCRATCH, RSCRATCH2,
|
||||
CallerSavedRegistersInUse());
|
||||
}
|
||||
}
|
||||
|
||||
void Jit64::cmpXX(UGeckoInstruction inst)
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/CoreTiming.h"
|
||||
#include "Core/Debugger/BranchWatch.h"
|
||||
#include "Core/HW/CPU.h"
|
||||
#include "Core/HW/Memmap.h"
|
||||
#include "Core/PowerPC/Jit64/RegCache/JitRegCache.h"
|
||||
@@ -300,6 +301,40 @@ void Jit64::dcbx(UGeckoInstruction inst)
|
||||
|
||||
// Load the loop_counter register with the amount of invalidations to execute.
|
||||
LEA(32, loop_counter, MDisp(RSCRATCH2, 1));
|
||||
|
||||
if (IsDebuggingEnabled())
|
||||
{
|
||||
const X64Reg bw_reg_a = reg_cycle_count, bw_reg_b = reg_downcount;
|
||||
const BitSet32 bw_caller_save = (CallerSavedRegistersInUse() | BitSet32{RSCRATCH2}) &
|
||||
~BitSet32{int(bw_reg_a), int(bw_reg_b)};
|
||||
|
||||
MOV(64, R(bw_reg_a), ImmPtr(&m_branch_watch));
|
||||
MOVZX(32, 8, bw_reg_b, MDisp(bw_reg_a, Core::BranchWatch::GetOffsetOfRecordingActive()));
|
||||
TEST(32, R(bw_reg_b), R(bw_reg_b));
|
||||
|
||||
FixupBranch branch_in = J_CC(CC_NZ, Jump::Near);
|
||||
SwitchToFarCode();
|
||||
SetJumpTarget(branch_in);
|
||||
|
||||
// Assert RSCRATCH2 won't be clobbered before it is moved from.
|
||||
static_assert(RSCRATCH2 != ABI_PARAM1);
|
||||
|
||||
ABI_PushRegistersAndAdjustStack(bw_caller_save, 0);
|
||||
MOV(64, R(ABI_PARAM1), R(bw_reg_a));
|
||||
// RSCRATCH2 holds the amount of faked branch watch hits. Move RSCRATCH2 first, because
|
||||
// ABI_PARAM2 clobbers RSCRATCH2 on Windows and ABI_PARAM3 clobbers RSCRATCH2 on Linux!
|
||||
MOV(32, R(ABI_PARAM4), R(RSCRATCH2));
|
||||
const PPCAnalyst::CodeOp& op = js.op[2];
|
||||
MOV(64, R(ABI_PARAM2), Imm64(Core::FakeBranchWatchCollectionKey{op.address, op.branchTo}));
|
||||
MOV(32, R(ABI_PARAM3), Imm32(op.inst.hex));
|
||||
ABI_CallFunction(m_ppc_state.msr.IR ? &Core::BranchWatch::HitVirtualTrue_fk_n :
|
||||
&Core::BranchWatch::HitPhysicalTrue_fk_n);
|
||||
ABI_PopRegistersAndAdjustStack(bw_caller_save, 0);
|
||||
|
||||
FixupBranch branch_out = J(Jump::Near);
|
||||
SwitchToNearCode();
|
||||
SetJumpTarget(branch_out);
|
||||
}
|
||||
}
|
||||
|
||||
X64Reg addr = RSCRATCH;
|
||||
|
||||
@@ -1181,7 +1181,22 @@ bool JitArm64::DoJit(u32 em_address, JitBlock* b, u32 nextPC)
|
||||
if (HandleFunctionHooking(op.address))
|
||||
break;
|
||||
|
||||
if (!op.skip)
|
||||
if (op.skip)
|
||||
{
|
||||
if (IsDebuggingEnabled())
|
||||
{
|
||||
// The only thing that currently sets op.skip is the BLR following optimization.
|
||||
// If any non-branch instruction starts setting that too, this will need to be changed.
|
||||
ASSERT(op.inst.hex == 0x4e800020);
|
||||
const ARM64Reg bw_reg_a = gpr.GetReg(), bw_reg_b = gpr.GetReg();
|
||||
const BitSet32 gpr_caller_save =
|
||||
gpr.GetCallerSavedUsed() & ~BitSet32{DecodeReg(bw_reg_a), DecodeReg(bw_reg_b)};
|
||||
WriteBranchWatch<true>(op.address, op.branchTo, op.inst, bw_reg_a, bw_reg_b,
|
||||
gpr_caller_save, fpr.GetCallerSavedUsed());
|
||||
gpr.Unlock(bw_reg_a, bw_reg_b);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((opinfo->flags & FL_USE_FPU) && !js.firstFPInstructionFound)
|
||||
{
|
||||
|
||||
@@ -315,6 +315,16 @@ protected:
|
||||
void MSRUpdated(u32 msr);
|
||||
void MSRUpdated(Arm64Gen::ARM64Reg msr);
|
||||
|
||||
// Branch Watch
|
||||
template <bool condition>
|
||||
void WriteBranchWatch(u32 origin, u32 destination, UGeckoInstruction inst,
|
||||
Arm64Gen::ARM64Reg reg_a, Arm64Gen::ARM64Reg reg_b,
|
||||
BitSet32 gpr_caller_save, BitSet32 fpr_caller_save);
|
||||
void WriteBranchWatchDestInRegister(u32 origin, Arm64Gen::ARM64Reg destination,
|
||||
UGeckoInstruction inst, Arm64Gen::ARM64Reg reg_a,
|
||||
Arm64Gen::ARM64Reg reg_b, BitSet32 gpr_caller_save,
|
||||
BitSet32 fpr_caller_save);
|
||||
|
||||
// Exits
|
||||
void
|
||||
WriteExit(u32 destination, bool LK = false, u32 exit_address_after_return = 0,
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "Core/Core.h"
|
||||
#include "Core/CoreTiming.h"
|
||||
#include "Core/Debugger/BranchWatch.h"
|
||||
#include "Core/PowerPC/JitArm64/JitArm64_RegCache.h"
|
||||
#include "Core/PowerPC/PPCTables.h"
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
@@ -74,6 +75,70 @@ void JitArm64::rfi(UGeckoInstruction inst)
|
||||
gpr.Unlock(WA);
|
||||
}
|
||||
|
||||
template <bool condition>
|
||||
void JitArm64::WriteBranchWatch(u32 origin, u32 destination, UGeckoInstruction inst, ARM64Reg reg_a,
|
||||
ARM64Reg reg_b, BitSet32 gpr_caller_save, BitSet32 fpr_caller_save)
|
||||
{
|
||||
const ARM64Reg branch_watch = EncodeRegTo64(reg_a);
|
||||
MOVP2R(branch_watch, &m_branch_watch);
|
||||
LDRB(IndexType::Unsigned, reg_b, branch_watch, Core::BranchWatch::GetOffsetOfRecordingActive());
|
||||
FixupBranch branch_over = CBZ(reg_b);
|
||||
|
||||
FixupBranch branch_in = B();
|
||||
SwitchToFarCode();
|
||||
SetJumpTarget(branch_in);
|
||||
|
||||
const ARM64Reg float_emit_tmp = EncodeRegTo64(reg_b);
|
||||
ABI_PushRegisters(gpr_caller_save);
|
||||
m_float_emit.ABI_PushRegisters(fpr_caller_save, float_emit_tmp);
|
||||
ABI_CallFunction(m_ppc_state.msr.IR ? (condition ? &Core::BranchWatch::HitVirtualTrue_fk :
|
||||
&Core::BranchWatch::HitVirtualFalse_fk) :
|
||||
(condition ? &Core::BranchWatch::HitPhysicalTrue_fk :
|
||||
&Core::BranchWatch::HitPhysicalFalse_fk),
|
||||
branch_watch, Core::FakeBranchWatchCollectionKey{origin, destination}, inst.hex);
|
||||
m_float_emit.ABI_PopRegisters(fpr_caller_save, float_emit_tmp);
|
||||
ABI_PopRegisters(gpr_caller_save);
|
||||
|
||||
FixupBranch branch_out = B();
|
||||
SwitchToNearCode();
|
||||
SetJumpTarget(branch_out);
|
||||
SetJumpTarget(branch_over);
|
||||
}
|
||||
|
||||
template void JitArm64::WriteBranchWatch<true>(u32, u32, UGeckoInstruction, ARM64Reg, ARM64Reg,
|
||||
BitSet32, BitSet32);
|
||||
template void JitArm64::WriteBranchWatch<false>(u32, u32, UGeckoInstruction, ARM64Reg, ARM64Reg,
|
||||
BitSet32, BitSet32);
|
||||
|
||||
void JitArm64::WriteBranchWatchDestInRegister(u32 origin, ARM64Reg destination,
|
||||
UGeckoInstruction inst, ARM64Reg reg_a,
|
||||
ARM64Reg reg_b, BitSet32 gpr_caller_save,
|
||||
BitSet32 fpr_caller_save)
|
||||
{
|
||||
const ARM64Reg branch_watch = EncodeRegTo64(reg_a);
|
||||
MOVP2R(branch_watch, &m_branch_watch);
|
||||
LDRB(IndexType::Unsigned, reg_b, branch_watch, Core::BranchWatch::GetOffsetOfRecordingActive());
|
||||
FixupBranch branch_over = CBZ(reg_b);
|
||||
|
||||
FixupBranch branch_in = B();
|
||||
SwitchToFarCode();
|
||||
SetJumpTarget(branch_in);
|
||||
|
||||
const ARM64Reg float_emit_tmp = EncodeRegTo64(reg_b);
|
||||
ABI_PushRegisters(gpr_caller_save);
|
||||
m_float_emit.ABI_PushRegisters(fpr_caller_save, float_emit_tmp);
|
||||
ABI_CallFunction(m_ppc_state.msr.IR ? &Core::BranchWatch::HitVirtualTrue :
|
||||
&Core::BranchWatch::HitPhysicalTrue,
|
||||
branch_watch, origin, destination, inst.hex);
|
||||
m_float_emit.ABI_PopRegisters(fpr_caller_save, float_emit_tmp);
|
||||
ABI_PopRegisters(gpr_caller_save);
|
||||
|
||||
FixupBranch branch_out = B();
|
||||
SwitchToNearCode();
|
||||
SetJumpTarget(branch_out);
|
||||
SetJumpTarget(branch_over);
|
||||
}
|
||||
|
||||
void JitArm64::bx(UGeckoInstruction inst)
|
||||
{
|
||||
INSTRUCTION_START
|
||||
@@ -89,6 +154,16 @@ void JitArm64::bx(UGeckoInstruction inst)
|
||||
|
||||
if (!js.isLastInstruction)
|
||||
{
|
||||
if (IsDebuggingEnabled())
|
||||
{
|
||||
const ARM64Reg WB = gpr.GetReg(), WC = gpr.GetReg();
|
||||
BitSet32 gpr_caller_save = gpr.GetCallerSavedUsed() & ~BitSet32{DecodeReg(WB), DecodeReg(WC)};
|
||||
if (WA != ARM64Reg::INVALID_REG && js.op->skipLRStack)
|
||||
gpr_caller_save[DecodeReg(WA)] = false;
|
||||
WriteBranchWatch<true>(js.compilerPC, js.op->branchTo, inst, WB, WC, gpr_caller_save,
|
||||
fpr.GetCallerSavedUsed());
|
||||
gpr.Unlock(WB, WC);
|
||||
}
|
||||
if (inst.LK && !js.op->skipLRStack)
|
||||
{
|
||||
// We have to fake the stack as the RET instruction was not
|
||||
@@ -108,22 +183,37 @@ void JitArm64::bx(UGeckoInstruction inst)
|
||||
|
||||
if (js.op->branchIsIdleLoop)
|
||||
{
|
||||
if (WA != ARM64Reg::INVALID_REG)
|
||||
gpr.Unlock(WA);
|
||||
if (WA == ARM64Reg::INVALID_REG)
|
||||
WA = gpr.GetReg();
|
||||
|
||||
if (IsDebuggingEnabled())
|
||||
{
|
||||
const ARM64Reg WB = gpr.GetReg();
|
||||
WriteBranchWatch<true>(js.compilerPC, js.op->branchTo, inst, WA, WB, {}, {});
|
||||
gpr.Unlock(WB);
|
||||
}
|
||||
|
||||
// make idle loops go faster
|
||||
ARM64Reg WB = gpr.GetReg();
|
||||
ARM64Reg XB = EncodeRegTo64(WB);
|
||||
ARM64Reg XA = EncodeRegTo64(WA);
|
||||
|
||||
MOVP2R(XB, &CoreTiming::GlobalIdle);
|
||||
BLR(XB);
|
||||
gpr.Unlock(WB);
|
||||
MOVP2R(XA, &CoreTiming::GlobalIdle);
|
||||
BLR(XA);
|
||||
gpr.Unlock(WA);
|
||||
|
||||
WriteExceptionExit(js.op->branchTo);
|
||||
return;
|
||||
}
|
||||
|
||||
WriteExit(js.op->branchTo, inst.LK, js.compilerPC + 4, inst.LK ? WA : ARM64Reg::INVALID_REG);
|
||||
if (IsDebuggingEnabled())
|
||||
{
|
||||
const ARM64Reg WB = gpr.GetReg(), WC = gpr.GetReg();
|
||||
const BitSet32 gpr_caller_save =
|
||||
WA != ARM64Reg::INVALID_REG ? BitSet32{DecodeReg(WA)} & CALLER_SAVED_GPRS : BitSet32{};
|
||||
WriteBranchWatch<true>(js.compilerPC, js.op->branchTo, inst, WB, WC, gpr_caller_save, {});
|
||||
gpr.Unlock(WB, WC);
|
||||
}
|
||||
WriteExit(js.op->branchTo, inst.LK, js.compilerPC + 4, WA);
|
||||
|
||||
if (WA != ARM64Reg::INVALID_REG)
|
||||
gpr.Unlock(WA);
|
||||
}
|
||||
@@ -134,7 +224,9 @@ void JitArm64::bcx(UGeckoInstruction inst)
|
||||
JITDISABLE(bJITBranchOff);
|
||||
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
ARM64Reg WB = inst.LK ? gpr.GetReg() : WA;
|
||||
ARM64Reg WB = inst.LK || IsDebuggingEnabled() ? gpr.GetReg() : WA;
|
||||
ARM64Reg WC = IsDebuggingEnabled() && inst.LK && !js.op->branchIsIdleLoop ? gpr.GetReg() :
|
||||
ARM64Reg::INVALID_REG;
|
||||
|
||||
FixupBranch pCTRDontBranch;
|
||||
if ((inst.BO & BO_DONT_DECREMENT_FLAG) == 0) // Decrement and test CTR
|
||||
@@ -166,6 +258,19 @@ void JitArm64::bcx(UGeckoInstruction inst)
|
||||
gpr.Flush(FlushMode::MaintainState, WB);
|
||||
fpr.Flush(FlushMode::MaintainState, ARM64Reg::INVALID_REG);
|
||||
|
||||
if (IsDebuggingEnabled())
|
||||
{
|
||||
ARM64Reg bw_reg_a, bw_reg_b;
|
||||
// WC is only allocated when WA is needed for WriteExit and cannot be clobbered.
|
||||
if (WC == ARM64Reg::INVALID_REG)
|
||||
bw_reg_a = WA, bw_reg_b = WB;
|
||||
else
|
||||
bw_reg_a = WB, bw_reg_b = WC;
|
||||
const BitSet32 gpr_caller_save =
|
||||
gpr.GetCallerSavedUsed() & ~BitSet32{DecodeReg(bw_reg_a), DecodeReg(bw_reg_b)};
|
||||
WriteBranchWatch<true>(js.compilerPC, js.op->branchTo, inst, bw_reg_a, bw_reg_b,
|
||||
gpr_caller_save, fpr.GetCallerSavedUsed());
|
||||
}
|
||||
if (js.op->branchIsIdleLoop)
|
||||
{
|
||||
// make idle loops go faster
|
||||
@@ -178,7 +283,7 @@ void JitArm64::bcx(UGeckoInstruction inst)
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteExit(js.op->branchTo, inst.LK, js.compilerPC + 4, inst.LK ? WA : ARM64Reg::INVALID_REG);
|
||||
WriteExit(js.op->branchTo, inst.LK, js.compilerPC + 4, WA);
|
||||
}
|
||||
|
||||
if ((inst.BO & BO_DONT_CHECK_CONDITION) == 0)
|
||||
@@ -186,12 +291,26 @@ void JitArm64::bcx(UGeckoInstruction inst)
|
||||
if ((inst.BO & BO_DONT_DECREMENT_FLAG) == 0)
|
||||
SetJumpTarget(pCTRDontBranch);
|
||||
|
||||
if (WC != ARM64Reg::INVALID_REG)
|
||||
gpr.Unlock(WC);
|
||||
|
||||
if (!analyzer.HasOption(PPCAnalyst::PPCAnalyzer::OPTION_CONDITIONAL_CONTINUE))
|
||||
{
|
||||
gpr.Flush(FlushMode::All, WA);
|
||||
fpr.Flush(FlushMode::All, ARM64Reg::INVALID_REG);
|
||||
if (IsDebuggingEnabled())
|
||||
{
|
||||
WriteBranchWatch<false>(js.compilerPC, js.compilerPC + 4, inst, WA, WB, {}, {});
|
||||
}
|
||||
WriteExit(js.compilerPC + 4);
|
||||
}
|
||||
else if (IsDebuggingEnabled())
|
||||
{
|
||||
const BitSet32 gpr_caller_save =
|
||||
gpr.GetCallerSavedUsed() & ~BitSet32{DecodeReg(WA), DecodeReg(WB)};
|
||||
WriteBranchWatch<false>(js.compilerPC, js.compilerPC + 4, inst, WA, WB, gpr_caller_save,
|
||||
fpr.GetCallerSavedUsed());
|
||||
}
|
||||
|
||||
gpr.Unlock(WA);
|
||||
if (WB != WA)
|
||||
@@ -231,7 +350,17 @@ void JitArm64::bcctrx(UGeckoInstruction inst)
|
||||
LDR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF_SPR(SPR_CTR));
|
||||
AND(WA, WA, LogicalImm(~0x3, GPRSize::B32));
|
||||
|
||||
WriteExit(WA, inst.LK_3, js.compilerPC + 4, inst.LK_3 ? WB : ARM64Reg::INVALID_REG);
|
||||
if (IsDebuggingEnabled())
|
||||
{
|
||||
const ARM64Reg WC = gpr.GetReg(), WD = gpr.GetReg();
|
||||
BitSet32 gpr_caller_save = BitSet32{DecodeReg(WA)};
|
||||
if (WB != ARM64Reg::INVALID_REG)
|
||||
gpr_caller_save[DecodeReg(WB)] = true;
|
||||
gpr_caller_save &= CALLER_SAVED_GPRS;
|
||||
WriteBranchWatchDestInRegister(js.compilerPC, WA, inst, WC, WD, gpr_caller_save, {});
|
||||
gpr.Unlock(WC, WD);
|
||||
}
|
||||
WriteExit(WA, inst.LK_3, js.compilerPC + 4, WB);
|
||||
|
||||
if (WB != ARM64Reg::INVALID_REG)
|
||||
gpr.Unlock(WB);
|
||||
@@ -247,7 +376,9 @@ void JitArm64::bclrx(UGeckoInstruction inst)
|
||||
(inst.BO & BO_DONT_DECREMENT_FLAG) == 0 || (inst.BO & BO_DONT_CHECK_CONDITION) == 0;
|
||||
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
ARM64Reg WB = conditional || inst.LK ? gpr.GetReg() : ARM64Reg::INVALID_REG;
|
||||
ARM64Reg WB =
|
||||
conditional || inst.LK || IsDebuggingEnabled() ? gpr.GetReg() : ARM64Reg::INVALID_REG;
|
||||
ARM64Reg WC = IsDebuggingEnabled() ? gpr.GetReg() : ARM64Reg::INVALID_REG;
|
||||
|
||||
FixupBranch pCTRDontBranch;
|
||||
if ((inst.BO & BO_DONT_DECREMENT_FLAG) == 0) // Decrement and test CTR
|
||||
@@ -281,6 +412,26 @@ void JitArm64::bclrx(UGeckoInstruction inst)
|
||||
gpr.Flush(conditional ? FlushMode::MaintainState : FlushMode::All, WB);
|
||||
fpr.Flush(conditional ? FlushMode::MaintainState : FlushMode::All, ARM64Reg::INVALID_REG);
|
||||
|
||||
if (IsDebuggingEnabled())
|
||||
{
|
||||
BitSet32 gpr_caller_save;
|
||||
BitSet32 fpr_caller_save;
|
||||
if (conditional)
|
||||
{
|
||||
gpr_caller_save = gpr.GetCallerSavedUsed() & ~BitSet32{DecodeReg(WB), DecodeReg(WC)};
|
||||
if (js.op->branchIsIdleLoop)
|
||||
gpr_caller_save[DecodeReg(WA)] = false;
|
||||
fpr_caller_save = fpr.GetCallerSavedUsed();
|
||||
}
|
||||
else
|
||||
{
|
||||
gpr_caller_save =
|
||||
js.op->branchIsIdleLoop ? BitSet32{} : BitSet32{DecodeReg(WA)} & CALLER_SAVED_GPRS;
|
||||
fpr_caller_save = {};
|
||||
}
|
||||
WriteBranchWatchDestInRegister(js.compilerPC, WA, inst, WB, WC, gpr_caller_save,
|
||||
fpr_caller_save);
|
||||
}
|
||||
if (js.op->branchIsIdleLoop)
|
||||
{
|
||||
// make idle loops go faster
|
||||
@@ -301,12 +452,26 @@ void JitArm64::bclrx(UGeckoInstruction inst)
|
||||
if ((inst.BO & BO_DONT_DECREMENT_FLAG) == 0)
|
||||
SetJumpTarget(pCTRDontBranch);
|
||||
|
||||
if (WC != ARM64Reg::INVALID_REG)
|
||||
gpr.Unlock(WC);
|
||||
|
||||
if (!analyzer.HasOption(PPCAnalyst::PPCAnalyzer::OPTION_CONDITIONAL_CONTINUE))
|
||||
{
|
||||
gpr.Flush(FlushMode::All, WA);
|
||||
fpr.Flush(FlushMode::All, ARM64Reg::INVALID_REG);
|
||||
if (IsDebuggingEnabled())
|
||||
{
|
||||
WriteBranchWatch<false>(js.compilerPC, js.compilerPC + 4, inst, WA, WB, {}, {});
|
||||
}
|
||||
WriteExit(js.compilerPC + 4);
|
||||
}
|
||||
else if (IsDebuggingEnabled())
|
||||
{
|
||||
const BitSet32 gpr_caller_save =
|
||||
gpr.GetCallerSavedUsed() & ~BitSet32{DecodeReg(WA), DecodeReg(WB)};
|
||||
WriteBranchWatch<false>(js.compilerPC, js.compilerPC + 4, inst, WA, WB, gpr_caller_save,
|
||||
fpr.GetCallerSavedUsed());
|
||||
}
|
||||
|
||||
gpr.Unlock(WA);
|
||||
if (WB != ARM64Reg::INVALID_REG)
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/CoreTiming.h"
|
||||
#include "Core/Debugger/BranchWatch.h"
|
||||
#include "Core/HW/DSP.h"
|
||||
#include "Core/HW/MMIO.h"
|
||||
#include "Core/HW/Memmap.h"
|
||||
@@ -769,18 +770,15 @@ void JitArm64::dcbx(UGeckoInstruction inst)
|
||||
js.op[1].inst.RA_6 == b && js.op[1].inst.RD_2 == b &&
|
||||
js.op[2].inst.hex == 0x4200fff8;
|
||||
|
||||
gpr.Lock(ARM64Reg::W0, ARM64Reg::W1);
|
||||
if (make_loop)
|
||||
gpr.Lock(ARM64Reg::W2);
|
||||
constexpr ARM64Reg WA = ARM64Reg::W0, WB = ARM64Reg::W1, loop_counter = ARM64Reg::W2;
|
||||
// Be careful, loop_counter is only locked when make_loop == true.
|
||||
gpr.Lock(WA, WB);
|
||||
|
||||
ARM64Reg WA = ARM64Reg::W0;
|
||||
|
||||
if (make_loop)
|
||||
gpr.BindToRegister(b, true);
|
||||
|
||||
ARM64Reg loop_counter = ARM64Reg::INVALID_REG;
|
||||
if (make_loop)
|
||||
{
|
||||
gpr.Lock(loop_counter);
|
||||
gpr.BindToRegister(b, true);
|
||||
|
||||
// We'll execute somewhere between one single cacheline invalidation and however many are needed
|
||||
// to reduce the downcount to zero, never exceeding the amount requested by the game.
|
||||
// To stay consistent with the rest of the code we adjust the involved registers (CTR and Rb)
|
||||
@@ -788,10 +786,8 @@ void JitArm64::dcbx(UGeckoInstruction inst)
|
||||
// bdnz afterwards! So if we invalidate a single cache line, we don't adjust the registers at
|
||||
// all, if we invalidate 2 cachelines we adjust the registers by one step, and so on.
|
||||
|
||||
ARM64Reg reg_cycle_count = gpr.GetReg();
|
||||
ARM64Reg reg_downcount = gpr.GetReg();
|
||||
loop_counter = ARM64Reg::W2;
|
||||
ARM64Reg WB = ARM64Reg::W1;
|
||||
const ARM64Reg reg_cycle_count = gpr.GetReg();
|
||||
const ARM64Reg reg_downcount = gpr.GetReg();
|
||||
|
||||
// Figure out how many loops we want to do.
|
||||
const u8 cycle_count_per_loop =
|
||||
@@ -828,11 +824,43 @@ void JitArm64::dcbx(UGeckoInstruction inst)
|
||||
// Load the loop_counter register with the amount of invalidations to execute.
|
||||
ADD(loop_counter, WA, 1);
|
||||
|
||||
if (IsDebuggingEnabled())
|
||||
{
|
||||
const ARM64Reg branch_watch = EncodeRegTo64(reg_cycle_count);
|
||||
MOVP2R(branch_watch, &m_branch_watch);
|
||||
LDRB(IndexType::Unsigned, WB, branch_watch, Core::BranchWatch::GetOffsetOfRecordingActive());
|
||||
FixupBranch branch_over = CBZ(WB);
|
||||
|
||||
FixupBranch branch_in = B();
|
||||
SwitchToFarCode();
|
||||
SetJumpTarget(branch_in);
|
||||
|
||||
const BitSet32 gpr_caller_save =
|
||||
gpr.GetCallerSavedUsed() &
|
||||
~BitSet32{DecodeReg(WB), DecodeReg(reg_cycle_count), DecodeReg(reg_downcount)};
|
||||
ABI_PushRegisters(gpr_caller_save);
|
||||
const ARM64Reg float_emit_tmp = EncodeRegTo64(WB);
|
||||
const BitSet32 fpr_caller_save = fpr.GetCallerSavedUsed();
|
||||
m_float_emit.ABI_PushRegisters(fpr_caller_save, float_emit_tmp);
|
||||
const PPCAnalyst::CodeOp& op = js.op[2];
|
||||
ABI_CallFunction(m_ppc_state.msr.IR ? &Core::BranchWatch::HitVirtualTrue_fk_n :
|
||||
&Core::BranchWatch::HitPhysicalTrue_fk_n,
|
||||
branch_watch, Core::FakeBranchWatchCollectionKey{op.address, op.branchTo},
|
||||
op.inst.hex, WA);
|
||||
m_float_emit.ABI_PopRegisters(fpr_caller_save, float_emit_tmp);
|
||||
ABI_PopRegisters(gpr_caller_save);
|
||||
|
||||
FixupBranch branch_out = B();
|
||||
SwitchToNearCode();
|
||||
SetJumpTarget(branch_out);
|
||||
SetJumpTarget(branch_over);
|
||||
}
|
||||
|
||||
gpr.Unlock(reg_cycle_count, reg_downcount);
|
||||
}
|
||||
|
||||
ARM64Reg effective_addr = ARM64Reg::W1;
|
||||
ARM64Reg physical_addr = gpr.GetReg();
|
||||
constexpr ARM64Reg effective_addr = WB;
|
||||
const ARM64Reg physical_addr = gpr.GetReg();
|
||||
|
||||
if (a)
|
||||
ADD(effective_addr, gpr.R(a), gpr.R(b));
|
||||
@@ -911,7 +939,7 @@ void JitArm64::dcbx(UGeckoInstruction inst)
|
||||
SwitchToNearCode();
|
||||
SetJumpTarget(near_addr);
|
||||
|
||||
gpr.Unlock(effective_addr, physical_addr, WA);
|
||||
gpr.Unlock(WA, WB, physical_addr);
|
||||
if (make_loop)
|
||||
gpr.Unlock(loop_counter);
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ void JitTrampoline(JitBase& jit, u32 em_address)
|
||||
|
||||
JitBase::JitBase(Core::System& system)
|
||||
: m_code_buffer(code_buffer_size), m_system(system), m_ppc_state(system.GetPPCState()),
|
||||
m_mmu(system.GetMMU())
|
||||
m_mmu(system.GetMMU()), m_branch_watch(system.GetPowerPC().GetBranchWatch())
|
||||
{
|
||||
m_registered_config_callback_id = CPUThreadConfigCallback::AddConfigChangedCallback([this] {
|
||||
if (DoesConfigNeedRefresh())
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user