mirror of
https://github.com/libretro/parallel-gx1.git
synced 2026-11-07 04:26:01 -10:00
Measure elapsed time by retro_run, not the host clock
FrameClock also sums frame lengths at the refresh rate the core reports, and the core-wide Clock (CommonTypes.h) reads that sum. Everything that measured elapsed time now follows the frontend: it advances a frame at a time and stands still while the frontend pauses. That covers input expression timers, cursor and gyro integration, Wiimote-as-device timeouts, the DSU client, VR controller velocity, Free Look, IOS socket timeouts, the BBA TCP timers, custom asset load times, OSD message lifetimes, Common::Timer and the Dolphin IOS device's elapsed time. Local steady_clock aliases are removed so nothing bypasses it. Calendar reads stay: the RTC's starting date, file and log timestamps, and generated file names still take the host's date and time. Deleted: SteadyAwakeClock, the debugger's unused CodeTrace, and the host-time diagnostics in IOS request handling and DVDThread.
This commit is contained in:
1 parent
1e2dbf6616
commit
c478df9181
27 files changed
+53
-580
No files matched your search
@@ -1407,7 +1407,6 @@ DOLPHIN_SOURCES_CXX += \
|
||||
$(CORE_DIR)/Source/Core/Core/CoreTiming.cpp \
|
||||
$(CORE_DIR)/Source/Core/Core/CPUThreadConfigCallback.cpp \
|
||||
$(CORE_DIR)/Source/Core/Core/Debugger/BranchWatch.cpp \
|
||||
$(CORE_DIR)/Source/Core/Core/Debugger/CodeTrace.cpp \
|
||||
$(CORE_DIR)/Source/Core/Core/Debugger/Debugger_SymbolMap.cpp \
|
||||
$(CORE_DIR)/Source/Core/Core/Debugger/Dump.cpp \
|
||||
$(CORE_DIR)/Source/Core/Core/Debugger/OSThread.cpp \
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
|
||||
#include "Common/FrameClock.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <tchar.h>
|
||||
#if defined(__MINGW32__)
|
||||
@@ -32,7 +34,19 @@ using s16 = std::int16_t;
|
||||
using s32 = std::int32_t;
|
||||
using s64 = std::int64_t;
|
||||
|
||||
using Clock = std::chrono::steady_clock;
|
||||
// The core's clock: elapsed time as retro_run has paced it (Common/FrameClock.h), never the
|
||||
// host's. It stands still while the frontend pauses and advances a frame at a time.
|
||||
struct FrameTimeClock
|
||||
{
|
||||
using rep = s64;
|
||||
using period = std::micro;
|
||||
using duration = std::chrono::duration<rep, period>;
|
||||
using time_point = std::chrono::time_point<FrameTimeClock>;
|
||||
static constexpr bool is_steady = true;
|
||||
static time_point now() { return time_point(duration(static_cast<rep>(frame_clock_time_us()))); }
|
||||
};
|
||||
|
||||
using Clock = FrameTimeClock;
|
||||
using TimePoint = Clock::time_point;
|
||||
using DT = Clock::duration;
|
||||
using DT_us = std::chrono::duration<double, std::micro>;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <rthreads/retro_eventcount.h>
|
||||
|
||||
static retro_atomic_64_t s_frame;
|
||||
static retro_atomic_64_t s_time_us;
|
||||
static retro_atomic_ptr_t s_tick; /* retro_eventcount_t*, made on first use */
|
||||
|
||||
static retro_eventcount_t *frame_clock_tick(void)
|
||||
@@ -27,8 +28,9 @@ static retro_eventcount_t *frame_clock_tick(void)
|
||||
return ec;
|
||||
}
|
||||
|
||||
void frame_clock_advance(void)
|
||||
void frame_clock_advance(unsigned frame_us)
|
||||
{
|
||||
retro_atomic_fetch_add_64(&s_time_us, (int64_t)frame_us);
|
||||
retro_atomic_fetch_add_64(&s_frame, 1);
|
||||
retro_eventcount_notify(frame_clock_tick());
|
||||
}
|
||||
@@ -38,6 +40,11 @@ uint64_t frame_clock_now(void)
|
||||
return (uint64_t)retro_atomic_load_acquire_64(&s_frame);
|
||||
}
|
||||
|
||||
uint64_t frame_clock_time_us(void)
|
||||
{
|
||||
return (uint64_t)retro_atomic_load_acquire_64(&s_time_us);
|
||||
}
|
||||
|
||||
bool frame_clock_wait_until(uint64_t target, const retro_atomic_int_t *running)
|
||||
{
|
||||
retro_eventcount_t *tick = frame_clock_tick();
|
||||
|
||||
@@ -18,11 +18,16 @@ extern "C" {
|
||||
* the frontend's pace, stop while it pauses, and never sleep or read a
|
||||
* clock. */
|
||||
|
||||
/* Called once per retro_run, by the frontend's thread. */
|
||||
void frame_clock_advance(void);
|
||||
/* Called once per retro_run, by the frontend's thread, with the length
|
||||
* of a frame at the refresh rate the core reports. */
|
||||
void frame_clock_advance(unsigned frame_us);
|
||||
|
||||
uint64_t frame_clock_now(void);
|
||||
|
||||
/* The sum of the frame lengths so far, in microseconds: the core's
|
||||
* idea of elapsed time, paced by the frontend. */
|
||||
uint64_t frame_clock_time_us(void);
|
||||
|
||||
/* Blocks until frame target has begun or *running reads zero. running
|
||||
* may be NULL. Returns false when no longer running. Whoever clears
|
||||
* *running calls frame_clock_wake_all afterwards. */
|
||||
|
||||
@@ -16,26 +16,15 @@
|
||||
|
||||
namespace Common
|
||||
{
|
||||
template <typename Clock, typename Duration>
|
||||
static typename Clock::rep time_now()
|
||||
{
|
||||
return std::chrono::time_point_cast<Duration>(Clock::now()).time_since_epoch().count();
|
||||
}
|
||||
|
||||
template <typename Duration>
|
||||
static auto steady_time_now()
|
||||
{
|
||||
return time_now<std::chrono::steady_clock, Duration>();
|
||||
}
|
||||
|
||||
// Elapsed time is the frontend's: the frame clock, not the host's.
|
||||
u64 Timer::NowUs()
|
||||
{
|
||||
return steady_time_now<std::chrono::microseconds>();
|
||||
return frame_clock_time_us();
|
||||
}
|
||||
|
||||
u64 Timer::NowMs()
|
||||
{
|
||||
return steady_time_now<std::chrono::milliseconds>();
|
||||
return frame_clock_time_us() / 1000;
|
||||
}
|
||||
|
||||
void Timer::Start()
|
||||
@@ -91,37 +80,4 @@ u64 Timer::GetLocalTimeSinceJan1970()
|
||||
#endif
|
||||
}
|
||||
|
||||
// Results are appropriately slewed on Linux, but not on Windows, macOS, or FreeBSD.
|
||||
// Clocks with that functionality seem to not be available there.
|
||||
auto SteadyAwakeClock::now() -> time_point
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
// The count is system time "in units of 100 nanoseconds".
|
||||
using InterruptDuration = std::chrono::duration<ULONGLONG, std::ratio<100, std::nano::den>::type>;
|
||||
|
||||
ULONGLONG interrupt_time{};
|
||||
if (!QueryUnbiasedInterruptTime(&interrupt_time))
|
||||
ERROR_LOG_FMT(COMMON, "QueryUnbiasedInterruptTime");
|
||||
|
||||
return time_point{InterruptDuration{interrupt_time}};
|
||||
#else
|
||||
// Note that Linux's CLOCK_MONOTONIC "does not count time that the system is suspended".
|
||||
// This is in contrast to the behavior on macOS and FreeBSD.
|
||||
static constexpr auto clock_id =
|
||||
#if defined(__linux__)
|
||||
CLOCK_MONOTONIC;
|
||||
#elif defined(__APPLE__)
|
||||
CLOCK_UPTIME_RAW;
|
||||
#else
|
||||
CLOCK_UPTIME;
|
||||
#endif
|
||||
|
||||
timespec ts{};
|
||||
if (clock_gettime(clock_id, &ts) != 0)
|
||||
ERROR_LOG_FMT(COMMON, "clock_gettime: {}", LastStrerrorString());
|
||||
|
||||
return time_point{std::chrono::seconds{ts.tv_sec} + std::chrono::nanoseconds{ts.tv_nsec}};
|
||||
#endif
|
||||
}
|
||||
|
||||
} // Namespace Common
|
||||
@@ -29,19 +29,4 @@ private:
|
||||
bool m_running{false};
|
||||
};
|
||||
|
||||
// Similar to std::chrono::steady_clock except this clock
|
||||
// specifically does *not* count time while the system is suspended.
|
||||
class SteadyAwakeClock
|
||||
{
|
||||
public:
|
||||
using rep = s64;
|
||||
using period = std::nano;
|
||||
using duration = std::chrono::duration<rep, period>;
|
||||
using time_point = std::chrono::time_point<SteadyAwakeClock>;
|
||||
|
||||
static constexpr bool is_steady = true;
|
||||
|
||||
static time_point now();
|
||||
};
|
||||
|
||||
} // Namespace Common
|
||||
@@ -1,388 +0,0 @@
|
||||
// Copyright 2022 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "Core/Debugger/CodeTrace.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <regex>
|
||||
|
||||
#include "Common/Contains.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/Debugger/PPCDebugInterface.h"
|
||||
#include "Core/HW/CPU.h"
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
#include "Core/System.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
bool IsInstructionLoadStore(std::string_view ins)
|
||||
{
|
||||
return (ins.starts_with('l') && !ins.starts_with("li")) || ins.starts_with("st") ||
|
||||
ins.starts_with("psq_l") || ins.starts_with("psq_s");
|
||||
}
|
||||
|
||||
u32 GetMemoryTargetSize(std::string_view instr)
|
||||
{
|
||||
// Word-size operations are taken as the default, check the others.
|
||||
auto op = instr.substr(0, 4);
|
||||
|
||||
constexpr char BYTE_TAG = 'b';
|
||||
constexpr char HALF_TAG = 'h';
|
||||
constexpr char DOUBLE_WORD_TAG = 'd';
|
||||
constexpr char PAIRED_TAG = 'p';
|
||||
|
||||
// Actual range is 0 to size - 1;
|
||||
if (op.contains(BYTE_TAG))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
else if (op.contains(HALF_TAG))
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
else if (op.contains(DOUBLE_WORD_TAG) || op.contains(PAIRED_TAG))
|
||||
{
|
||||
return 8;
|
||||
}
|
||||
|
||||
return 4;
|
||||
}
|
||||
|
||||
bool CompareMemoryTargetToTracked(const std::string& instr, const u32 mem_target,
|
||||
const std::set<u32>& mem_tracked)
|
||||
{
|
||||
const auto it_lower = mem_tracked.lower_bound(mem_target);
|
||||
|
||||
if (it_lower == mem_tracked.end())
|
||||
return false;
|
||||
if (*it_lower == mem_target)
|
||||
return true;
|
||||
|
||||
// If the base value doesn't hit, still need to check if longer values overlap.
|
||||
return *it_lower < mem_target + GetMemoryTargetSize(instr);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void CodeTrace::SetRegTracked(const std::string& reg)
|
||||
{
|
||||
m_reg_autotrack.push_back(reg);
|
||||
}
|
||||
|
||||
InstructionAttributes CodeTrace::GetInstructionAttributes(const TraceOutput& instruction) const
|
||||
{
|
||||
// Slower process of breaking down saved instruction. Only used when stepping through code if a
|
||||
// decision has to be made, otherwise used afterwards on a log file.
|
||||
InstructionAttributes tmp_attributes;
|
||||
tmp_attributes.instruction = instruction.instruction;
|
||||
tmp_attributes.address = instruction.address;
|
||||
std::string instr = instruction.instruction;
|
||||
std::smatch match;
|
||||
|
||||
// Convert sp, rtoc, and ps to r1, r2, and F#. ps is handled like a float operation.
|
||||
static const std::regex replace_sp("(\\W)sp");
|
||||
instr = std::regex_replace(instr, replace_sp, "$1r1");
|
||||
static const std::regex replace_rtoc("rtoc");
|
||||
instr = std::regex_replace(instr, replace_rtoc, "r2");
|
||||
static const std::regex replace_ps("(\\W)p(\\d+)");
|
||||
instr = std::regex_replace(instr, replace_ps, "$1f$2");
|
||||
|
||||
// Pull all register numbers out and store them. Limited to Reg0 if ps operation, as ps get
|
||||
// too complicated to track easily.
|
||||
// ex: add r4, r5, r6 -> r4 = Reg0, r5 = Reg1, r6 = Reg2. Reg0 is always the target register.
|
||||
static const std::regex regis(
|
||||
"\\W([rfp]\\d+)[^r^f]*(?:([rf]\\d+))?[^r^f\\d]*(?:([rf]\\d+))?[^r^f\\d]*(?:([rf]\\d+))?",
|
||||
std::regex::optimize);
|
||||
|
||||
if (std::regex_search(instr, match, regis))
|
||||
{
|
||||
tmp_attributes.reg0 = match.str(1);
|
||||
if (match[2].matched)
|
||||
tmp_attributes.reg1 = match.str(2);
|
||||
if (match[3].matched)
|
||||
tmp_attributes.reg2 = match.str(3);
|
||||
if (match[4].matched)
|
||||
tmp_attributes.reg3 = match.str(4);
|
||||
|
||||
if (instruction.memory_target)
|
||||
{
|
||||
tmp_attributes.memory_target = instruction.memory_target;
|
||||
tmp_attributes.memory_target_size = GetMemoryTargetSize(instr);
|
||||
|
||||
if (instr.starts_with("st") || instr.starts_with("psq_s"))
|
||||
tmp_attributes.is_store = true;
|
||||
else
|
||||
tmp_attributes.is_load = true;
|
||||
}
|
||||
}
|
||||
|
||||
return tmp_attributes;
|
||||
}
|
||||
|
||||
TraceOutput CodeTrace::SaveCurrentInstruction(const Core::CPUThreadGuard& guard) const
|
||||
{
|
||||
auto& system = guard.GetSystem();
|
||||
auto& power_pc = system.GetPowerPC();
|
||||
auto& ppc_state = power_pc.GetPPCState();
|
||||
auto& debug_interface = power_pc.GetDebugInterface();
|
||||
|
||||
// Quickly save instruction and memory target for fast logging.
|
||||
TraceOutput output;
|
||||
const std::string instr = debug_interface.Disassemble(&guard, ppc_state.pc);
|
||||
output.instruction = instr;
|
||||
output.address = ppc_state.pc;
|
||||
|
||||
if (IsInstructionLoadStore(output.instruction))
|
||||
output.memory_target = debug_interface.GetMemoryAddressFromInstruction(instr);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
AutoStepResults CodeTrace::AutoStepping(const Core::CPUThreadGuard& guard, bool continue_previous,
|
||||
AutoStop stop_on)
|
||||
{
|
||||
AutoStepResults results;
|
||||
|
||||
if (m_recording)
|
||||
return results;
|
||||
|
||||
TraceOutput pc_instr = SaveCurrentInstruction(guard);
|
||||
const InstructionAttributes instr = GetInstructionAttributes(pc_instr);
|
||||
|
||||
// Not an instruction we should start autostepping from (ie branches).
|
||||
if (instr.reg0.empty() && !continue_previous)
|
||||
return results;
|
||||
|
||||
m_recording = true;
|
||||
|
||||
// Once autostep stops, it can be told to continue running without resetting the tracked
|
||||
// registers and memory.
|
||||
if (!continue_previous)
|
||||
{
|
||||
m_reg_autotrack.clear();
|
||||
m_mem_autotrack.clear();
|
||||
m_reg_autotrack.push_back(instr.reg0);
|
||||
|
||||
// It wouldn't necessarily be wrong to also record the memory of a load operation, as the
|
||||
// value exists there too. May or may not be desirable depending on task. Leaving it out.
|
||||
if (instr.is_store)
|
||||
{
|
||||
const u32 size = GetMemoryTargetSize(instr.instruction);
|
||||
for (u32 i = 0; i < size; i++)
|
||||
m_mem_autotrack.insert(instr.memory_target.value() + i);
|
||||
}
|
||||
}
|
||||
|
||||
// Count is important for feedback on how much work was done.
|
||||
|
||||
HitType hit = HitType::SKIP;
|
||||
HitType stop_condition = HitType::SAVELOAD;
|
||||
|
||||
// Could use bit flags, but I organized it to have decreasing levels of verbosity, so the
|
||||
// less-than comparison ignores what is needed for the current usage.
|
||||
if (stop_on == AutoStop::Always)
|
||||
stop_condition = HitType::SAVELOAD;
|
||||
else if (stop_on == AutoStop::Used)
|
||||
stop_condition = HitType::PASSIVE;
|
||||
else if (stop_on == AutoStop::Changed)
|
||||
stop_condition = HitType::ACTIVE;
|
||||
|
||||
auto& power_pc = guard.GetSystem().GetPowerPC();
|
||||
using clock = std::chrono::steady_clock;
|
||||
clock::time_point timeout = clock::now() + std::chrono::seconds(4);
|
||||
|
||||
PowerPC::CoreMode old_mode = power_pc.GetMode();
|
||||
power_pc.SetMode(PowerPC::CoreMode::Interpreter);
|
||||
|
||||
do
|
||||
{
|
||||
power_pc.SingleStep();
|
||||
|
||||
pc_instr = SaveCurrentInstruction(guard);
|
||||
hit = TraceLogic(pc_instr);
|
||||
results.count += 1;
|
||||
} while (clock::now() < timeout && hit < stop_condition &&
|
||||
!(m_reg_autotrack.empty() && m_mem_autotrack.empty()));
|
||||
|
||||
// Report the timeout to the caller.
|
||||
if (clock::now() >= timeout)
|
||||
results.timed_out = true;
|
||||
|
||||
power_pc.SetMode(old_mode);
|
||||
m_recording = false;
|
||||
|
||||
results.reg_tracked = m_reg_autotrack;
|
||||
results.mem_tracked = m_mem_autotrack;
|
||||
|
||||
// Doesn't currently need to report the hit type to the caller. Denoting when the reg and mem
|
||||
// trackers are both empty is important, as it means our target was overwritten and can no longer
|
||||
// be tracked. Different actions can be taken on a timeout vs empty trackers, so they are reported
|
||||
// individually.
|
||||
return results;
|
||||
}
|
||||
|
||||
HitType CodeTrace::TraceLogic(const TraceOutput& current_instr, bool first_hit)
|
||||
{
|
||||
// Tracks the original value that is in the targeted register or memory through loads, stores,
|
||||
// register moves, and value changes. Also finds when it is used. ps operations are not fully
|
||||
// supported. -ux memory instructions may need special cases.
|
||||
// Should not be called if reg and mem tracked are empty.
|
||||
|
||||
// Using a std::set because it can easily insert the memory range being accessed without
|
||||
// causing duplicates, and quickly erases all members of the memory range without caring if the
|
||||
// element actually exists.
|
||||
|
||||
bool mem_hit = false;
|
||||
if (current_instr.memory_target && !m_mem_autotrack.empty())
|
||||
{
|
||||
mem_hit = CompareMemoryTargetToTracked(current_instr.instruction, *current_instr.memory_target,
|
||||
m_mem_autotrack);
|
||||
}
|
||||
|
||||
// Optimization for tracking a memory target when no registers are being tracked.
|
||||
if (m_reg_autotrack.empty() && !mem_hit)
|
||||
return HitType::SKIP;
|
||||
|
||||
// Break instruction down into parts to be analyzed.
|
||||
const InstructionAttributes instr = GetInstructionAttributes(current_instr);
|
||||
|
||||
// Not an instruction we care about (branches).
|
||||
if (instr.reg0.empty())
|
||||
return HitType::SKIP;
|
||||
|
||||
// The reg_itr will be used later for erasing.
|
||||
auto reg_itr = std::ranges::find(m_reg_autotrack, instr.reg0);
|
||||
const bool match_reg123 =
|
||||
(!instr.reg1.empty() && Common::Contains(m_reg_autotrack, instr.reg1)) ||
|
||||
(!instr.reg2.empty() && Common::Contains(m_reg_autotrack, instr.reg2)) ||
|
||||
(!instr.reg3.empty() && Common::Contains(m_reg_autotrack, instr.reg3));
|
||||
const bool match_reg0 = reg_itr != m_reg_autotrack.end();
|
||||
|
||||
if (!match_reg0 && !match_reg123 && !mem_hit)
|
||||
return HitType::SKIP;
|
||||
|
||||
// Checks if the instruction is a type that needs special handling.
|
||||
const auto CompareInstruction = [](std::string_view instruction, const auto& type_compare) {
|
||||
return std::ranges::any_of(
|
||||
type_compare, [&instruction](std::string_view s) { return instruction.starts_with(s); });
|
||||
};
|
||||
|
||||
// Exclusions from updating tracking logic. mt operations are too complex and specialized.
|
||||
// Combiner used later.
|
||||
static const std::array<std::string_view, 3> exclude{"dc", "ic", "mt"};
|
||||
static const std::array<std::string_view, 2> compare{"c", "fc"};
|
||||
|
||||
// rlwimi, at least, can preserve parts of the target register. Not sure if rldimi can too or if
|
||||
// there are any others like this.
|
||||
static const std::array<std::string_view, 1> combiner{"rlwimi"};
|
||||
|
||||
static const std::array<std::string_view, 2> mover{"mr", "fmr"};
|
||||
|
||||
// Link register for when r0 gets overwritten
|
||||
if (instr.instruction.starts_with("mflr") && match_reg0)
|
||||
{
|
||||
m_reg_autotrack.erase(reg_itr);
|
||||
return HitType::OVERWRITE;
|
||||
}
|
||||
if (instr.instruction.starts_with("mtlr") && match_reg0)
|
||||
{
|
||||
// LR is not something tracked
|
||||
return HitType::MOVED;
|
||||
}
|
||||
|
||||
if (CompareInstruction(instr.instruction, exclude))
|
||||
return HitType::SKIP;
|
||||
else if (CompareInstruction(instr.instruction, compare))
|
||||
return HitType::PASSIVE;
|
||||
else if (match_reg123 && !match_reg0 && (instr.is_store || instr.is_load))
|
||||
return HitType::POINTER;
|
||||
|
||||
// Update tracking logic. At this point a memory or register hit happened.
|
||||
// Save/Load
|
||||
if (instr.memory_target)
|
||||
{
|
||||
if (mem_hit)
|
||||
{
|
||||
// If hit a tracked memory. Load -> Add register to tracked. Store -> Remove tracked memory
|
||||
// if overwritten.
|
||||
|
||||
if (instr.is_load && !match_reg0)
|
||||
{
|
||||
m_reg_autotrack.push_back(instr.reg0);
|
||||
return HitType::SAVELOAD;
|
||||
}
|
||||
else if (instr.is_store && !match_reg0)
|
||||
{
|
||||
// On First Hit it wouldn't necessarily be wrong to track the register, which contains the
|
||||
// same value. A matter of preference.
|
||||
if (first_hit)
|
||||
return HitType::SAVELOAD;
|
||||
|
||||
for (u32 i = 0; i < instr.memory_target_size; i++)
|
||||
m_mem_autotrack.erase(*instr.memory_target + i);
|
||||
|
||||
return HitType::OVERWRITE;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If reg0 and store/load are both already tracked, do nothing.
|
||||
return HitType::SAVELOAD;
|
||||
}
|
||||
}
|
||||
else if (instr.is_store && match_reg0)
|
||||
{
|
||||
// If store to untracked memory, then track memory.
|
||||
for (u32 i = 0; i < instr.memory_target_size; i++)
|
||||
m_mem_autotrack.insert(*instr.memory_target + i);
|
||||
|
||||
return HitType::SAVELOAD;
|
||||
}
|
||||
else if (instr.is_load && match_reg0)
|
||||
{
|
||||
// Not wrong to track load memory_target here. Preference.
|
||||
if (first_hit)
|
||||
return HitType::SAVELOAD;
|
||||
|
||||
// If untracked load is overwriting tracked register, then remove register
|
||||
m_reg_autotrack.erase(reg_itr);
|
||||
return HitType::OVERWRITE;
|
||||
}
|
||||
}
|
||||
else if (!match_reg0 && !match_reg123)
|
||||
{
|
||||
// Skip if no matches. Happens most often.
|
||||
return HitType::SKIP;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If tracked register data is being stored in a new register, save new register.
|
||||
if (match_reg123 && !match_reg0)
|
||||
{
|
||||
m_reg_autotrack.push_back(instr.reg0);
|
||||
|
||||
// This should include any instruction that can reach this point and is not ACTIVE. Can only
|
||||
// think of mr at this time.
|
||||
if (CompareInstruction(instr.instruction, mover))
|
||||
return HitType::MOVED;
|
||||
|
||||
return HitType::ACTIVE;
|
||||
}
|
||||
// If tracked register is overwritten, stop tracking.
|
||||
else if (match_reg0 && !match_reg123)
|
||||
{
|
||||
if (CompareInstruction(instr.instruction, combiner) || first_hit)
|
||||
return HitType::UPDATED;
|
||||
|
||||
m_reg_autotrack.erase(reg_itr);
|
||||
return HitType::OVERWRITE;
|
||||
}
|
||||
else if (match_reg0 && match_reg123)
|
||||
{
|
||||
// Or moved
|
||||
return HitType::UPDATED;
|
||||
}
|
||||
}
|
||||
|
||||
// Should not reach this
|
||||
return HitType::SKIP;
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
// Copyright 2022 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
namespace Core
|
||||
{
|
||||
class CPUThreadGuard;
|
||||
}
|
||||
|
||||
struct InstructionAttributes
|
||||
{
|
||||
u32 address = 0;
|
||||
std::string instruction = "";
|
||||
std::string reg0 = "";
|
||||
std::string reg1 = "";
|
||||
std::string reg2 = "";
|
||||
std::string reg3 = "";
|
||||
std::optional<u32> memory_target = std::nullopt;
|
||||
u32 memory_target_size = 4;
|
||||
bool is_store = false;
|
||||
bool is_load = false;
|
||||
};
|
||||
|
||||
struct TraceOutput
|
||||
{
|
||||
u32 address = 0;
|
||||
std::optional<u32> memory_target = std::nullopt;
|
||||
std::string instruction;
|
||||
};
|
||||
|
||||
struct AutoStepResults
|
||||
{
|
||||
std::vector<std::string> reg_tracked;
|
||||
std::set<u32> mem_tracked;
|
||||
u32 count = 0;
|
||||
bool timed_out = false;
|
||||
bool trackers_empty = false;
|
||||
};
|
||||
|
||||
enum class HitType : u32
|
||||
{
|
||||
SKIP = (1 << 0), // Not a hit
|
||||
OVERWRITE = (1 << 1), // Tracked value gets overwritten by untracked. Typically skipped.
|
||||
MOVED = (1 << 2), // Target duplicated to another register, unchanged.
|
||||
SAVELOAD = (1 << 3), // Target saved or loaded. Priority over Pointer.
|
||||
POINTER = (1 << 4), // Target used as pointer/offset for save or load
|
||||
PASSIVE = (1 << 5), // Conditional, etc, but not pointer. Unchanged
|
||||
ACTIVE = (1 << 6), // Math, etc. Changed.
|
||||
UPDATED = (1 << 7), // Masked or math without changing register.
|
||||
};
|
||||
|
||||
class CodeTrace
|
||||
{
|
||||
public:
|
||||
enum class AutoStop
|
||||
{
|
||||
Always,
|
||||
Used,
|
||||
Changed
|
||||
};
|
||||
|
||||
void SetRegTracked(const std::string& reg);
|
||||
AutoStepResults AutoStepping(const Core::CPUThreadGuard& guard, bool continue_previous = false,
|
||||
AutoStop stop_on = AutoStop::Always);
|
||||
|
||||
private:
|
||||
InstructionAttributes GetInstructionAttributes(const TraceOutput& line) const;
|
||||
TraceOutput SaveCurrentInstruction(const Core::CPUThreadGuard& guard) const;
|
||||
HitType TraceLogic(const TraceOutput& current_instr, bool first_hit = false);
|
||||
|
||||
bool m_recording = false;
|
||||
std::vector<std::string> m_reg_autotrack;
|
||||
std::set<u32> m_mem_autotrack;
|
||||
};
|
||||
@@ -248,11 +248,11 @@ void FreeLookController::UpdateInput(CameraControllerInput* camera_controller)
|
||||
if (m_last_free_look_rotate_time)
|
||||
{
|
||||
using seconds = std::chrono::duration<float, std::ratio<1>>;
|
||||
dt = std::chrono::duration_cast<seconds>(std::chrono::steady_clock::now() -
|
||||
dt = std::chrono::duration_cast<seconds>(Clock::now() -
|
||||
*m_last_free_look_rotate_time)
|
||||
.count();
|
||||
}
|
||||
m_last_free_look_rotate_time = std::chrono::steady_clock::now();
|
||||
m_last_free_look_rotate_time = Clock::now();
|
||||
|
||||
const auto gyro_motion_rad_velocity =
|
||||
m_rotation_gyro->GetState() ? *m_rotation_gyro->GetState() : Common::Vec3{};
|
||||
|
||||
@@ -61,5 +61,5 @@ private:
|
||||
ControllerEmu::IMUGyroscope* m_rotation_gyro;
|
||||
|
||||
const unsigned int m_index;
|
||||
std::optional<std::chrono::steady_clock::time_point> m_last_free_look_rotate_time;
|
||||
std::optional<Clock::time_point> m_last_free_look_rotate_time;
|
||||
};
|
||||
@@ -264,7 +264,6 @@ void DVDThread::StartReadInternal(bool copy_to_ram, u32 output_address, u64 dvd_
|
||||
request.id = id;
|
||||
|
||||
request.time_started_ticks = core_timing.GetTicks();
|
||||
request.realtime_started_us = Common::Timer::NowUs();
|
||||
|
||||
m_dvd_thread.Push(std::move(request));
|
||||
core_timing.ScheduleEvent(ticks_until_completion, m_finish_read, id);
|
||||
@@ -307,12 +306,7 @@ void DVDThread::FinishRead(u64 id, s64 cycles_late)
|
||||
const ReadRequest& request = result.first;
|
||||
const std::vector<u8>& buffer = result.second;
|
||||
|
||||
DEBUG_LOG_FMT(DVDINTERFACE,
|
||||
"Disc has been read. Real time: {} us. "
|
||||
"Real time including delay: {} us. "
|
||||
"Emulated time including delay: {} us.",
|
||||
request.realtime_done_us - request.realtime_started_us,
|
||||
Common::Timer::NowUs() - request.realtime_started_us,
|
||||
DEBUG_LOG_FMT(DVDINTERFACE, "Disc has been read. Emulated time including delay: {} us.",
|
||||
(m_system.GetCoreTiming().GetTicks() - request.time_started_ticks) /
|
||||
(m_system.GetSystemTimers().GetTicksPerSecond() / 1000000));
|
||||
|
||||
@@ -349,7 +343,6 @@ void DVDThread::ProcessReadRequest(ReadRequest&& request)
|
||||
if (!m_disc->Read(request.dvd_offset, request.length, buffer.data(), request.partition))
|
||||
buffer.resize(0);
|
||||
|
||||
request.realtime_done_us = Common::Timer::NowUs();
|
||||
|
||||
ResultNode* const node =
|
||||
new ResultNode{{nullptr}, ReadResult(std::move(request), std::move(buffer))};
|
||||
|
||||
@@ -116,8 +116,6 @@ private:
|
||||
|
||||
// Only used for logging
|
||||
u64 time_started_ticks = 0;
|
||||
u64 realtime_started_us = 0;
|
||||
u64 realtime_done_us = 0;
|
||||
};
|
||||
|
||||
void ProcessReadRequest(ReadRequest&& read_request);
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace
|
||||
u64 GetTickCountStd()
|
||||
{
|
||||
using namespace std::chrono;
|
||||
return duration_cast<milliseconds>(steady_clock::now().time_since_epoch()).count();
|
||||
return duration_cast<milliseconds>(Clock::now().time_since_epoch()).count();
|
||||
}
|
||||
|
||||
std::vector<u8> BuildFINFrame(StackRef* ref)
|
||||
|
||||
@@ -724,7 +724,6 @@ std::optional<IPCReply> EmulationKernel::HandleIPCCommand(const Request& request
|
||||
return IPCReply{IPC_EINVAL, 550_tbticks};
|
||||
|
||||
std::optional<IPCReply> ret;
|
||||
const u64 wall_time_before = Common::Timer::NowUs();
|
||||
|
||||
switch (request.command)
|
||||
{
|
||||
@@ -755,13 +754,6 @@ std::optional<IPCReply> EmulationKernel::HandleIPCCommand(const Request& request
|
||||
break;
|
||||
}
|
||||
|
||||
const u64 wall_time_after = Common::Timer::NowUs();
|
||||
constexpr u64 BLOCKING_IPC_COMMAND_THRESHOLD_US = 2000;
|
||||
if (wall_time_after - wall_time_before > BLOCKING_IPC_COMMAND_THRESHOLD_US)
|
||||
{
|
||||
WARN_LOG_FMT(IOS, "Previous request to device {} blocked emulation for {} microseconds.",
|
||||
device->GetDeviceName(), wall_time_after - wall_time_before);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -349,7 +349,7 @@ void WiiSocket::Update(bool read, bool write, bool except)
|
||||
case -SO_EAGAIN:
|
||||
case -SO_EALREADY:
|
||||
case -SO_EINPROGRESS:
|
||||
if (std::chrono::steady_clock::now() > GetTimeout())
|
||||
if (Clock::now() > GetTimeout())
|
||||
{
|
||||
ReturnValue = -SO_ENETUNREACH;
|
||||
ResetTimeout();
|
||||
@@ -841,7 +841,7 @@ const WiiSocket::Timeout& WiiSocket::GetTimeout()
|
||||
{
|
||||
if (!timeout.has_value())
|
||||
{
|
||||
timeout = std::chrono::steady_clock::now() +
|
||||
timeout = Clock::now() +
|
||||
std::chrono::seconds(Config::Get(Config::MAIN_NETWORK_TIMEOUT));
|
||||
}
|
||||
return *timeout;
|
||||
@@ -1050,7 +1050,7 @@ void WiiSockMan::UpdatePollCommands()
|
||||
if (pending_polls.empty())
|
||||
return;
|
||||
|
||||
const auto now = std::chrono::high_resolution_clock::now();
|
||||
const auto now = Clock::now();
|
||||
const auto elapsed_d = std::chrono::duration_cast<std::chrono::milliseconds>(now - last_time);
|
||||
const auto elapsed = elapsed_d.count();
|
||||
last_time = now;
|
||||
|
||||
@@ -177,7 +177,7 @@ public:
|
||||
WiiSocket& operator=(WiiSocket&&) = delete;
|
||||
|
||||
private:
|
||||
using Timeout = std::chrono::time_point<std::chrono::steady_clock>;
|
||||
using Timeout = Clock::time_point;
|
||||
struct sockop
|
||||
{
|
||||
Request request;
|
||||
@@ -300,7 +300,7 @@ private:
|
||||
std::unordered_map<s32, WiiSocket> WiiSockets;
|
||||
s32 errno_last = 0;
|
||||
std::vector<PollCommand> pending_polls;
|
||||
std::chrono::time_point<std::chrono::high_resolution_clock> last_time =
|
||||
std::chrono::high_resolution_clock::now();
|
||||
Clock::time_point last_time =
|
||||
Clock::now();
|
||||
};
|
||||
} // namespace IOS::HLE
|
||||
@@ -66,8 +66,6 @@ struct JitBlock : public JitBlockData
|
||||
// Software profiling data for JIT block.
|
||||
struct ProfileData
|
||||
{
|
||||
using Clock = std::chrono::steady_clock;
|
||||
|
||||
static void BeginProfiling(ProfileData* data);
|
||||
static void EndProfiling(ProfileData* data, u32 downcount_amount);
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ void JitInterface::JitBlockLogDump(const Core::CPUThreadGuard& guard, std::FILE*
|
||||
if (m_jit->IsProfilingEnabled())
|
||||
{
|
||||
u64 overall_cycles_spent = 0;
|
||||
JitBlock::ProfileData::Clock::duration overall_time_spent = {};
|
||||
Clock::duration overall_time_spent = {};
|
||||
m_jit->GetBlockCache()->RunOnBlocks(guard, [&](const JitBlock& block) {
|
||||
overall_cycles_spent += block.profile_data->cycles_spent;
|
||||
overall_time_spent += block.profile_data->time_spent;
|
||||
@@ -146,7 +146,7 @@ void JitInterface::JitBlockLogDump(const Core::CPUThreadGuard& guard, std::FILE*
|
||||
|
||||
const double cycles_percent =
|
||||
overall_cycles_spent == 0 ? double{} : 100.0 * data->cycles_spent / overall_cycles_spent;
|
||||
const double time_percent = overall_time_spent == JitBlock::ProfileData::Clock::duration{} ?
|
||||
const double time_percent = overall_time_spent == Clock::duration{} ?
|
||||
double{} :
|
||||
100.0 * data->time_spent.count() / overall_time_spent.count();
|
||||
const double cycles_average = data->run_count == 0 ?
|
||||
|
||||
@@ -246,8 +246,9 @@ void retro_reset(void)
|
||||
|
||||
void retro_run(void)
|
||||
{
|
||||
// One retro_run is one frame of the core's background timebase.
|
||||
frame_clock_advance();
|
||||
// One retro_run is one frame of the core's timebase, as long as the frontend was told.
|
||||
const double frame_hz = Libretro::g_core_refresh_rate > 1.0 ? Libretro::g_core_refresh_rate : 60.0;
|
||||
frame_clock_advance(static_cast<unsigned>(std::lround(1e6 / frame_hz)));
|
||||
|
||||
Libretro::Input::InitSensors();
|
||||
Libretro::Options::CheckForUpdatedVariables();
|
||||
|
||||
@@ -32,7 +32,7 @@ static bool s_frontend_flat = false;
|
||||
// heading the cursor is measured from.
|
||||
static bool s_have_controller_velocity = false;
|
||||
static Common::Vec3 s_controller_velocity;
|
||||
static std::chrono::steady_clock::time_point s_controller_time;
|
||||
static Clock::time_point s_controller_time;
|
||||
static bool s_have_point_forward = false;
|
||||
static Common::Quaternion s_point_forward = Common::Quaternion::Identity();
|
||||
|
||||
@@ -168,7 +168,7 @@ static void UpdateVRMotion()
|
||||
const Common::Quaternion to_remote = remote.Conjugate();
|
||||
|
||||
Common::Vec3 acceleration(0.0f, G, 0.0f);
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const auto now = Clock::now();
|
||||
if (aim.flags & RETRO_VR_POSE_LINEAR_VELOCITY_VALID)
|
||||
{
|
||||
const Common::Vec3 velocity(aim.linear_velocity[0], aim.linear_velocity[1],
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
|
||||
namespace ciface::ExpressionParser
|
||||
{
|
||||
using Clock = std::chrono::steady_clock;
|
||||
using FSec = std::chrono::duration<ControlState>;
|
||||
|
||||
// usage: toggle(toggle_state_input, [clear_state_input])
|
||||
|
||||
@@ -60,7 +60,6 @@ private:
|
||||
|
||||
int m_auto_hide_timer = AUTO_HIDE_MS;
|
||||
|
||||
using Clock = std::chrono::steady_clock;
|
||||
Clock::time_point m_last_update;
|
||||
|
||||
SettingValue<double> m_yaw_setting;
|
||||
|
||||
@@ -31,7 +31,6 @@ public:
|
||||
bool IsCalibrating() const;
|
||||
|
||||
private:
|
||||
using Clock = std::chrono::steady_clock;
|
||||
|
||||
bool AreInputsBound() const;
|
||||
bool CanCalibrate() const;
|
||||
|
||||
@@ -85,7 +85,6 @@ jmethodID s_dolphin_vibrator_manager_get_vibrator_ids;
|
||||
|
||||
jintArray s_keycodes_array;
|
||||
|
||||
using Clock = std::chrono::steady_clock;
|
||||
constexpr Clock::duration ACTIVE_INPUT_TIMEOUT = std::chrono::milliseconds(1000);
|
||||
|
||||
std::unordered_map<jint, ciface::Core::DeviceQualifier> s_device_id_to_device_qualifier;
|
||||
|
||||
+1
-1
@@ -40,7 +40,7 @@ const Config::Info<bool> SERVERS_ENABLED{{Config::System::DualShockUDPClient, "S
|
||||
} // namespace Settings
|
||||
|
||||
// Clock type used for querying timeframes
|
||||
using SteadyClock = std::chrono::steady_clock;
|
||||
using SteadyClock = Clock;
|
||||
|
||||
class Device final : public Core::Device
|
||||
{
|
||||
|
||||
@@ -38,7 +38,6 @@ public:
|
||||
Core::DeviceRemoval UpdateInput() override;
|
||||
|
||||
private:
|
||||
using Clock = std::chrono::steady_clock;
|
||||
|
||||
enum class ExtensionID
|
||||
{
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace VideoCommon
|
||||
class CustomAsset
|
||||
{
|
||||
public:
|
||||
using ClockType = std::chrono::steady_clock;
|
||||
using ClockType = Clock;
|
||||
using TimeType = ClockType::time_point;
|
||||
|
||||
CustomAsset(std::shared_ptr<CustomAssetLibrary> library, CustomAssetLibrary::AssetID asset_id,
|
||||
|
||||
Reference in new issue
Block a user