2020-12-05 13:08:24 +01:00
|
|
|
#include "stdafx.h"
|
2018-09-29 00:12:00 +02:00
|
|
|
#include "CPUThread.h"
|
2021-03-30 17:31:46 +02:00
|
|
|
#include "CPUDisAsm.h"
|
2018-09-29 00:12:00 +02:00
|
|
|
|
2014-06-02 19:27:24 +02:00
|
|
|
#include "Emu/System.h"
|
2020-02-15 23:36:20 +01:00
|
|
|
#include "Emu/system_config.h"
|
2018-09-25 22:34:45 +02:00
|
|
|
#include "Emu/Memory/vm_locking.h"
|
2017-04-02 20:10:06 +02:00
|
|
|
#include "Emu/IdManager.h"
|
2019-10-08 02:19:59 +02:00
|
|
|
#include "Emu/GDB.h"
|
2018-10-11 00:17:19 +02:00
|
|
|
#include "Emu/Cell/PPUThread.h"
|
|
|
|
|
#include "Emu/Cell/SPUThread.h"
|
2021-01-22 09:11:54 +01:00
|
|
|
#include "Emu/RSX/RSXThread.h"
|
2020-10-18 14:00:10 +02:00
|
|
|
#include "Emu/perf_meter.hpp"
|
2013-11-03 20:23:16 +01:00
|
|
|
|
2020-11-19 14:05:08 +01:00
|
|
|
#include "util/asm.hpp"
|
2019-08-20 18:07:03 +02:00
|
|
|
#include <thread>
|
2019-10-14 19:41:31 +02:00
|
|
|
#include <unordered_map>
|
|
|
|
|
#include <map>
|
2019-08-20 18:07:03 +02:00
|
|
|
|
2021-12-30 17:39:18 +01:00
|
|
|
#if defined(ARCH_X64)
|
2020-12-21 15:12:05 +01:00
|
|
|
#include <emmintrin.h>
|
2021-12-30 17:39:18 +01:00
|
|
|
#endif
|
2020-12-21 15:12:05 +01:00
|
|
|
|
2017-02-09 23:51:29 +01:00
|
|
|
DECLARE(cpu_thread::g_threads_created){0};
|
|
|
|
|
DECLARE(cpu_thread::g_threads_deleted){0};
|
2020-10-09 19:33:12 +02:00
|
|
|
DECLARE(cpu_thread::g_suspend_counter){0};
|
2016-05-13 16:01:48 +02:00
|
|
|
|
2020-01-31 14:43:59 +01:00
|
|
|
LOG_CHANNEL(profiler);
|
2020-02-01 08:43:43 +01:00
|
|
|
LOG_CHANNEL(sys_log, "SYS");
|
2020-01-31 14:43:59 +01:00
|
|
|
|
2020-11-19 07:12:01 +01:00
|
|
|
static thread_local u32 s_tls_thread_slot = -1;
|
2020-10-10 08:32:23 +02:00
|
|
|
|
2020-11-14 08:56:05 +01:00
|
|
|
// Suspend counter stamp
|
|
|
|
|
static thread_local u64 s_tls_sctr = -1;
|
|
|
|
|
|
2020-10-29 03:09:21 +01:00
|
|
|
extern thread_local void(*g_tls_log_control)(const char* fmt, u64 progress);
|
|
|
|
|
|
2017-03-11 00:14:48 +01:00
|
|
|
template <>
|
2016-08-09 16:14:41 +02:00
|
|
|
void fmt_class_string<cpu_flag>::format(std::string& out, u64 arg)
|
2016-08-03 22:51:05 +02:00
|
|
|
{
|
2016-08-09 16:14:41 +02:00
|
|
|
format_enum(out, arg, [](cpu_flag f)
|
2016-08-07 21:01:27 +02:00
|
|
|
{
|
|
|
|
|
switch (f)
|
|
|
|
|
{
|
2017-02-09 23:51:29 +01:00
|
|
|
case cpu_flag::stop: return "STOP";
|
|
|
|
|
case cpu_flag::exit: return "EXIT";
|
2019-06-06 20:32:35 +02:00
|
|
|
case cpu_flag::wait: return "w";
|
2020-10-25 23:16:16 +01:00
|
|
|
case cpu_flag::temp: return "t";
|
2019-06-06 20:32:35 +02:00
|
|
|
case cpu_flag::pause: return "p";
|
2017-02-09 23:51:29 +01:00
|
|
|
case cpu_flag::suspend: return "s";
|
|
|
|
|
case cpu_flag::ret: return "ret";
|
|
|
|
|
case cpu_flag::signal: return "sig";
|
2017-03-11 00:14:48 +01:00
|
|
|
case cpu_flag::memory: return "mem";
|
2021-10-05 19:10:05 +02:00
|
|
|
case cpu_flag::pending: return "pend";
|
2017-02-22 11:10:55 +01:00
|
|
|
case cpu_flag::dbg_global_pause: return "G-PAUSE";
|
2017-02-09 23:51:29 +01:00
|
|
|
case cpu_flag::dbg_pause: return "PAUSE";
|
|
|
|
|
case cpu_flag::dbg_step: return "STEP";
|
2016-08-09 16:14:41 +02:00
|
|
|
case cpu_flag::__bitset_enum_max: break;
|
2016-08-07 21:01:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return unknown;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<>
|
2016-08-09 16:14:41 +02:00
|
|
|
void fmt_class_string<bs_t<cpu_flag>>::format(std::string& out, u64 arg)
|
2016-08-07 21:01:27 +02:00
|
|
|
{
|
2016-08-09 16:14:41 +02:00
|
|
|
format_bitset(out, arg, "[", "|", "]", &fmt_class_string<cpu_flag>::format);
|
2016-08-03 22:51:05 +02:00
|
|
|
}
|
|
|
|
|
|
2019-10-14 19:41:31 +02:00
|
|
|
// CPU profiler thread
|
|
|
|
|
struct cpu_prof
|
|
|
|
|
{
|
|
|
|
|
// PPU/SPU id enqueued for registration
|
|
|
|
|
lf_queue<u32> registered;
|
|
|
|
|
|
|
|
|
|
struct sample_info
|
|
|
|
|
{
|
|
|
|
|
// Block occurences: name -> sample_count
|
|
|
|
|
std::unordered_map<u64, u64, value_hash<u64>> freq;
|
|
|
|
|
|
|
|
|
|
// Total number of samples
|
|
|
|
|
u64 samples = 0, idle = 0;
|
|
|
|
|
|
2022-05-03 17:37:48 +02:00
|
|
|
// Avoid printing replicas
|
|
|
|
|
bool printed = false;
|
2019-10-14 19:41:31 +02:00
|
|
|
|
|
|
|
|
void reset()
|
|
|
|
|
{
|
|
|
|
|
freq.clear();
|
|
|
|
|
samples = 0;
|
|
|
|
|
idle = 0;
|
2022-05-03 17:37:48 +02:00
|
|
|
printed = false;
|
2019-10-14 19:41:31 +02:00
|
|
|
}
|
|
|
|
|
|
2022-05-03 17:37:48 +02:00
|
|
|
static std::string format(const std::multimap<u64, u64, std::greater<u64>>& chart, u64 samples, u64 idle, bool extended_print = false)
|
2019-10-14 19:41:31 +02:00
|
|
|
{
|
|
|
|
|
// Print results
|
|
|
|
|
std::string results;
|
2022-05-03 17:37:48 +02:00
|
|
|
results.reserve(extended_print ? 10100 : 5100);
|
2019-10-14 19:41:31 +02:00
|
|
|
|
|
|
|
|
// Fraction of non-idle samples
|
|
|
|
|
const f64 busy = 1. * (samples - idle) / samples;
|
|
|
|
|
|
2020-03-17 18:10:24 +01:00
|
|
|
for (auto& [count, name] : chart)
|
2019-10-14 19:41:31 +02:00
|
|
|
{
|
|
|
|
|
const f64 _frac = count / busy / samples;
|
|
|
|
|
|
|
|
|
|
// Print only 7 hash characters out of 11 (which covers roughly 48 bits)
|
|
|
|
|
fmt::append(results, "\n\t[%s", fmt::base57(be_t<u64>{name}));
|
|
|
|
|
results.resize(results.size() - 4);
|
|
|
|
|
|
|
|
|
|
// Print chunk address from lowest 16 bits
|
|
|
|
|
fmt::append(results, "...chunk-0x%05x]: %.4f%% (%u)", (name & 0xffff) * 4, _frac * 100., count);
|
|
|
|
|
|
2022-05-03 17:37:48 +02:00
|
|
|
if (results.size() >= (extended_print ? 10000 : 5000))
|
2019-10-14 19:41:31 +02:00
|
|
|
{
|
|
|
|
|
// Stop printing after reaching some arbitrary limit in characters
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-03 17:37:48 +02:00
|
|
|
return results;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Print info
|
|
|
|
|
void print(const std::shared_ptr<cpu_thread>& ptr)
|
|
|
|
|
{
|
|
|
|
|
if (printed || samples == idle)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Make reversed map: sample_count -> name
|
|
|
|
|
std::multimap<u64, u64, std::greater<u64>> chart;
|
|
|
|
|
|
|
|
|
|
for (auto& [name, count] : freq)
|
|
|
|
|
{
|
|
|
|
|
chart.emplace(count, name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Print results
|
|
|
|
|
const std::string results = format(chart, samples, idle);
|
|
|
|
|
profiler.notice("Thread \"%s\" [0x%08x]: %u samples (%.4f%% idle):%s", ptr->get_name(), ptr->id, samples, 100. * idle / samples, results);
|
|
|
|
|
|
|
|
|
|
printed = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void print_all(const std::unordered_map<std::shared_ptr<cpu_thread>, sample_info>& threads)
|
|
|
|
|
{
|
|
|
|
|
std::multimap<u64, u64, std::greater<u64>> chart;
|
|
|
|
|
|
|
|
|
|
std::unordered_map<u64, u64, value_hash<u64>> freq;
|
|
|
|
|
|
|
|
|
|
u64 samples = 0, idle = 0;
|
|
|
|
|
|
|
|
|
|
for (auto& [_, info] : threads)
|
|
|
|
|
{
|
|
|
|
|
// This function collects thread information regardless of 'printed' member state
|
|
|
|
|
for (auto& [name, count] : info.freq)
|
|
|
|
|
{
|
|
|
|
|
freq[name] += count;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
samples += info.samples;
|
|
|
|
|
idle += info.idle;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (auto& [name, count] : freq)
|
|
|
|
|
{
|
|
|
|
|
chart.emplace(count, name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (samples == idle)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const std::string results = format(chart, samples, idle, true);
|
|
|
|
|
profiler.notice("All Threads: %u samples (%.4f%% idle):%s", samples, 100. * idle / samples, results);
|
2019-10-14 19:41:31 +02:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
void operator()()
|
|
|
|
|
{
|
2022-05-03 17:37:48 +02:00
|
|
|
std::unordered_map<std::shared_ptr<cpu_thread>, sample_info> threads;
|
2019-10-14 19:41:31 +02:00
|
|
|
|
|
|
|
|
while (thread_ctrl::state() != thread_state::aborting)
|
|
|
|
|
{
|
|
|
|
|
bool flush = false;
|
|
|
|
|
|
|
|
|
|
// Handle registration channel
|
|
|
|
|
for (u32 id : registered.pop_all())
|
|
|
|
|
{
|
|
|
|
|
if (id == 0)
|
|
|
|
|
{
|
|
|
|
|
// Handle id zero as a command to flush results
|
|
|
|
|
flush = true;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::shared_ptr<cpu_thread> ptr;
|
|
|
|
|
|
|
|
|
|
if (id >> 24 == 1)
|
|
|
|
|
{
|
|
|
|
|
ptr = idm::get<named_thread<ppu_thread>>(id);
|
|
|
|
|
}
|
|
|
|
|
else if (id >> 24 == 2)
|
|
|
|
|
{
|
|
|
|
|
ptr = idm::get<named_thread<spu_thread>>(id);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2020-01-31 14:43:59 +01:00
|
|
|
profiler.error("Invalid Thread ID: 0x%08x", id);
|
2019-10-14 19:41:31 +02:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-14 14:09:20 +02:00
|
|
|
if (ptr && cpu_flag::exit - ptr->state)
|
2019-10-14 19:41:31 +02:00
|
|
|
{
|
2022-05-03 17:37:48 +02:00
|
|
|
auto [found, add] = threads.try_emplace(std::move(ptr));
|
2019-10-14 19:41:31 +02:00
|
|
|
|
|
|
|
|
if (!add)
|
|
|
|
|
{
|
2022-05-03 17:37:48 +02:00
|
|
|
// Overwritten (impossible?): print previous data
|
|
|
|
|
found->second.print(found->first);
|
2019-10-14 19:41:31 +02:00
|
|
|
found->second.reset();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (threads.empty())
|
|
|
|
|
{
|
|
|
|
|
// Wait for messages if no work (don't waste CPU)
|
2021-02-13 15:50:07 +01:00
|
|
|
thread_ctrl::wait_on(registered, nullptr);
|
2019-10-14 19:41:31 +02:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Sample active threads
|
2022-05-03 17:37:48 +02:00
|
|
|
for (auto& [ptr, info] : threads)
|
2019-10-14 19:41:31 +02:00
|
|
|
{
|
2022-05-03 17:37:48 +02:00
|
|
|
if (cpu_flag::exit - ptr->state)
|
2019-10-14 19:41:31 +02:00
|
|
|
{
|
|
|
|
|
// Get short function hash
|
2022-05-03 17:37:48 +02:00
|
|
|
const u64 name = atomic_storage<u64>::load(ptr->block_hash);
|
2019-10-14 19:41:31 +02:00
|
|
|
|
|
|
|
|
// Append occurrence
|
|
|
|
|
info.samples++;
|
|
|
|
|
|
2022-05-03 17:37:48 +02:00
|
|
|
if (auto state = +ptr->state; !::is_paused(state) && !::is_stopped(state) && cpu_flag::wait - state)
|
2019-10-14 19:41:31 +02:00
|
|
|
{
|
|
|
|
|
info.freq[name]++;
|
|
|
|
|
|
|
|
|
|
// Append verification time to fixed common name 0000000...chunk-0x3fffc
|
2021-07-31 10:10:05 +02:00
|
|
|
if (name >> 16 && (name & 0xffff) == 0)
|
2019-10-14 19:41:31 +02:00
|
|
|
info.freq[0xffff]++;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
info.idle++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
2022-05-03 17:37:48 +02:00
|
|
|
{
|
|
|
|
|
info.print(ptr);
|
|
|
|
|
}
|
2019-10-14 19:41:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (flush)
|
|
|
|
|
{
|
2020-01-31 14:43:59 +01:00
|
|
|
profiler.success("Flushing profiling results...");
|
2019-10-14 19:41:31 +02:00
|
|
|
|
|
|
|
|
// Print all results and cleanup
|
2022-05-03 17:37:48 +02:00
|
|
|
for (auto& [ptr, info] : threads)
|
2019-10-14 19:41:31 +02:00
|
|
|
{
|
2022-05-03 17:37:48 +02:00
|
|
|
info.print(ptr);
|
2019-10-14 19:41:31 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Wait, roughly for 20µs
|
|
|
|
|
thread_ctrl::wait_for(20, false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Print all remaining results
|
2022-05-03 17:37:48 +02:00
|
|
|
for (auto& [ptr, info] : threads)
|
2019-10-14 19:41:31 +02:00
|
|
|
{
|
2022-05-03 17:37:48 +02:00
|
|
|
info.print(ptr);
|
2019-10-14 19:41:31 +02:00
|
|
|
}
|
2022-05-03 17:37:48 +02:00
|
|
|
|
|
|
|
|
sample_info::print_all(threads);
|
2019-10-14 19:41:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static constexpr auto thread_name = "CPU Profiler"sv;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
using cpu_profiler = named_thread<cpu_prof>;
|
|
|
|
|
|
2021-03-17 21:49:43 +01:00
|
|
|
thread_local DECLARE(cpu_thread::g_tls_this_thread) = nullptr;
|
2015-09-26 22:46:04 +02:00
|
|
|
|
2020-11-19 07:12:01 +01:00
|
|
|
// Total number of CPU threads
|
|
|
|
|
static atomic_t<u64, 64> s_cpu_counter{0};
|
2020-03-09 17:18:39 +01:00
|
|
|
|
2020-11-19 07:12:01 +01:00
|
|
|
// List of posted tasks for suspend_all
|
2021-01-12 11:01:06 +01:00
|
|
|
//static atomic_t<cpu_thread::suspend_work*> s_cpu_work[128]{};
|
2020-10-09 19:33:12 +02:00
|
|
|
|
2020-11-19 07:12:01 +01:00
|
|
|
// Linked list of pushed tasks for suspend_all
|
|
|
|
|
static atomic_t<cpu_thread::suspend_work*> s_pushed{};
|
2020-03-09 17:18:39 +01:00
|
|
|
|
2020-11-19 07:12:01 +01:00
|
|
|
// Lock for suspend_all operations
|
|
|
|
|
static shared_mutex s_cpu_lock;
|
2020-10-10 08:32:23 +02:00
|
|
|
|
2020-11-19 07:12:01 +01:00
|
|
|
// Bit allocator for threads which need to be suspended
|
2020-11-19 14:05:08 +01:00
|
|
|
static atomic_t<u128> s_cpu_bits{};
|
2020-03-09 17:18:39 +01:00
|
|
|
|
2020-11-19 07:12:01 +01:00
|
|
|
// List of active threads which need to be suspended
|
2020-11-19 14:05:08 +01:00
|
|
|
static atomic_t<cpu_thread*> s_cpu_list[128]{};
|
2020-03-09 17:18:39 +01:00
|
|
|
|
2020-11-19 07:12:01 +01:00
|
|
|
namespace cpu_counter
|
|
|
|
|
{
|
|
|
|
|
void add(cpu_thread* _this) noexcept
|
2020-03-09 17:18:39 +01:00
|
|
|
{
|
2021-01-29 07:45:58 +01:00
|
|
|
switch (_this->id_type())
|
|
|
|
|
{
|
|
|
|
|
case 1:
|
|
|
|
|
case 2:
|
|
|
|
|
break;
|
|
|
|
|
default: return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-19 07:12:01 +01:00
|
|
|
std::lock_guard lock(s_cpu_lock);
|
2020-10-10 08:32:23 +02:00
|
|
|
|
2020-11-19 07:12:01 +01:00
|
|
|
u32 id = -1;
|
2020-03-09 17:18:39 +01:00
|
|
|
|
2020-11-19 07:12:01 +01:00
|
|
|
for (u64 i = 0;; i++)
|
2020-03-09 17:18:39 +01:00
|
|
|
{
|
2020-11-19 14:05:08 +01:00
|
|
|
const auto [bits, ok] = s_cpu_bits.fetch_op([](u128& bits)
|
2020-03-09 17:18:39 +01:00
|
|
|
{
|
|
|
|
|
if (~bits) [[likely]]
|
|
|
|
|
{
|
|
|
|
|
// Set lowest clear bit
|
|
|
|
|
bits |= bits + 1;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (ok) [[likely]]
|
|
|
|
|
{
|
|
|
|
|
// Get actual slot number
|
2020-11-19 14:05:08 +01:00
|
|
|
id = utils::ctz128(~bits);
|
2020-10-10 08:32:23 +02:00
|
|
|
|
|
|
|
|
// Register thread
|
2020-11-19 07:12:01 +01:00
|
|
|
if (s_cpu_list[id].compare_and_swap_test(nullptr, _this)) [[likely]]
|
2020-10-10 08:32:23 +02:00
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-19 07:12:01 +01:00
|
|
|
sys_log.fatal("Unexpected slot registration failure (%u).", id);
|
|
|
|
|
id = -1;
|
2020-10-10 08:32:23 +02:00
|
|
|
continue;
|
2020-03-09 17:18:39 +01:00
|
|
|
}
|
2019-06-06 20:32:35 +02:00
|
|
|
|
2020-11-19 07:12:01 +01:00
|
|
|
if (i > 50)
|
|
|
|
|
{
|
|
|
|
|
sys_log.fatal("Too many threads.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
busy_wait(300);
|
2020-10-10 08:32:23 +02:00
|
|
|
}
|
2019-06-06 20:32:35 +02:00
|
|
|
|
2020-11-19 07:12:01 +01:00
|
|
|
s_tls_thread_slot = id;
|
2020-03-09 17:18:39 +01:00
|
|
|
}
|
2019-06-06 20:32:35 +02:00
|
|
|
|
2020-11-19 14:05:08 +01:00
|
|
|
static void remove_cpu_bit(u32 bit)
|
|
|
|
|
{
|
|
|
|
|
s_cpu_bits.atomic_op([=](u128& val)
|
|
|
|
|
{
|
|
|
|
|
val &= ~(u128{1} << (bit % 128));
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-19 07:12:01 +01:00
|
|
|
void remove(cpu_thread* _this) noexcept
|
2020-03-09 17:18:39 +01:00
|
|
|
{
|
2020-11-22 10:57:50 +01:00
|
|
|
// Return if not registered
|
|
|
|
|
const u32 slot = s_tls_thread_slot;
|
|
|
|
|
|
|
|
|
|
if (slot == umax)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-19 07:12:01 +01:00
|
|
|
if (slot >= std::size(s_cpu_list))
|
2020-11-12 21:24:35 +01:00
|
|
|
{
|
2020-12-09 16:04:52 +01:00
|
|
|
sys_log.fatal("Index out of bounds (%u).", slot);
|
2020-11-12 21:24:35 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 01:48:09 +01:00
|
|
|
// Asynchronous unregister
|
2020-11-19 07:12:01 +01:00
|
|
|
if (!s_cpu_list[slot].compare_and_swap_test(_this, nullptr))
|
2020-10-10 08:32:23 +02:00
|
|
|
{
|
2020-03-09 17:18:39 +01:00
|
|
|
sys_log.fatal("Inconsistency for array slot %u", slot);
|
2020-10-10 08:32:23 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-19 14:05:08 +01:00
|
|
|
remove_cpu_bit(slot);
|
2020-10-10 08:32:23 +02:00
|
|
|
|
2020-11-19 07:12:01 +01:00
|
|
|
s_tls_thread_slot = -1;
|
2020-03-09 17:18:39 +01:00
|
|
|
}
|
|
|
|
|
|
2020-11-19 07:12:01 +01:00
|
|
|
template <typename F>
|
2020-11-19 14:05:08 +01:00
|
|
|
u128 for_all_cpu(/*mutable*/ u128 copy, F func) noexcept
|
2019-06-06 20:32:35 +02:00
|
|
|
{
|
2020-11-19 14:05:08 +01:00
|
|
|
for (u128 bits = copy; bits; bits &= bits - 1)
|
2019-06-06 20:32:35 +02:00
|
|
|
{
|
2020-11-19 14:05:08 +01:00
|
|
|
const u32 index = utils::ctz128(bits);
|
2016-04-25 12:49:12 +02:00
|
|
|
|
2020-11-19 07:12:01 +01:00
|
|
|
if (cpu_thread* cpu = s_cpu_list[index].load())
|
2019-06-06 20:32:35 +02:00
|
|
|
{
|
2020-11-19 07:12:01 +01:00
|
|
|
if constexpr (std::is_invocable_v<F, cpu_thread*, u32>)
|
2020-10-10 08:32:23 +02:00
|
|
|
{
|
2020-11-19 07:12:01 +01:00
|
|
|
if (!func(cpu, index))
|
2020-11-19 14:05:08 +01:00
|
|
|
copy &= ~(u128{1} << index);
|
2020-10-10 08:32:23 +02:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if constexpr (std::is_invocable_v<F, cpu_thread*>)
|
|
|
|
|
{
|
2020-11-19 07:12:01 +01:00
|
|
|
if (!func(cpu))
|
2020-11-19 14:05:08 +01:00
|
|
|
copy &= ~(u128{1} << index);
|
2020-10-10 08:32:23 +02:00
|
|
|
continue;
|
|
|
|
|
}
|
2020-11-19 07:12:01 +01:00
|
|
|
|
|
|
|
|
sys_log.fatal("cpu_counter::for_all_cpu: bad callback");
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2020-11-19 14:05:08 +01:00
|
|
|
copy &= ~(u128{1} << index);
|
2019-06-06 20:32:35 +02:00
|
|
|
}
|
|
|
|
|
}
|
2020-11-19 07:12:01 +01:00
|
|
|
|
|
|
|
|
return copy;
|
2019-06-06 20:32:35 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void cpu_thread::operator()()
|
|
|
|
|
{
|
2021-03-17 21:49:43 +01:00
|
|
|
g_tls_this_thread = this;
|
2015-11-26 09:06:29 +01:00
|
|
|
|
2021-03-17 17:28:03 +01:00
|
|
|
if (g_cfg.core.thread_scheduler != thread_scheduler_mode::os)
|
2018-10-11 00:17:19 +02:00
|
|
|
{
|
|
|
|
|
thread_ctrl::set_thread_affinity_mask(thread_ctrl::get_affinity_mask(id_type() == 1 ? thread_class::ppu : thread_class::spu));
|
|
|
|
|
}
|
2019-10-26 22:51:38 +02:00
|
|
|
|
2021-02-03 19:14:31 +01:00
|
|
|
while (!g_fxo->is_init<cpu_profiler>())
|
2020-03-09 17:18:39 +01:00
|
|
|
{
|
2021-01-30 15:25:21 +01:00
|
|
|
if (Emu.IsStopped())
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-09 17:18:39 +01:00
|
|
|
// Can we have a little race, right? First thread is started concurrently with g_fxo->init()
|
2021-02-21 14:43:02 +01:00
|
|
|
thread_ctrl::wait_for(1000);
|
2020-03-09 17:18:39 +01:00
|
|
|
}
|
|
|
|
|
|
2020-04-10 20:48:32 +02:00
|
|
|
switch (id_type())
|
2019-10-14 19:41:31 +02:00
|
|
|
{
|
2020-04-12 18:10:40 +02:00
|
|
|
case 1:
|
|
|
|
|
{
|
2021-03-02 12:59:19 +01:00
|
|
|
//g_fxo->get<cpu_profiler>().registered.push(id);
|
2020-04-10 20:48:32 +02:00
|
|
|
break;
|
2020-04-12 18:10:40 +02:00
|
|
|
}
|
2020-04-10 20:48:32 +02:00
|
|
|
case 2:
|
2020-04-12 18:10:40 +02:00
|
|
|
{
|
2020-04-10 20:48:32 +02:00
|
|
|
if (g_cfg.core.spu_prof)
|
|
|
|
|
{
|
2021-03-02 12:59:19 +01:00
|
|
|
g_fxo->get<cpu_profiler>().registered.push(id);
|
2020-04-10 20:48:32 +02:00
|
|
|
}
|
2020-04-12 18:10:40 +02:00
|
|
|
|
2020-04-10 20:48:32 +02:00
|
|
|
break;
|
2020-04-12 18:10:40 +02:00
|
|
|
}
|
|
|
|
|
default: break;
|
2019-10-14 19:41:31 +02:00
|
|
|
}
|
|
|
|
|
|
2019-06-06 20:32:35 +02:00
|
|
|
// Register thread in g_cpu_array
|
2020-11-19 07:12:01 +01:00
|
|
|
s_cpu_counter++;
|
2019-06-06 20:32:35 +02:00
|
|
|
|
2022-04-22 19:57:22 +02:00
|
|
|
atomic_wait_engine::set_notify_callback(g_use_rtm || id_type() != 1 /* PPU */ ? nullptr : +[](const void*, u64 progress)
|
2020-10-26 02:02:39 +01:00
|
|
|
{
|
|
|
|
|
static thread_local bool wait_set = false;
|
|
|
|
|
|
|
|
|
|
cpu_thread* _cpu = get_current_cpu_thread();
|
|
|
|
|
|
|
|
|
|
// Wait flag isn't set asynchronously so this should be thread-safe
|
2021-09-17 11:01:29 +02:00
|
|
|
if (progress == 0 && _cpu->state.none_of(cpu_flag::wait + cpu_flag::temp))
|
2020-10-26 02:02:39 +01:00
|
|
|
{
|
|
|
|
|
// Operation just started and syscall is imminent
|
|
|
|
|
_cpu->state += cpu_flag::wait + cpu_flag::temp;
|
|
|
|
|
wait_set = true;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (progress == umax && std::exchange(wait_set, false))
|
|
|
|
|
{
|
|
|
|
|
// Operation finished: need to clean wait flag
|
2020-12-09 08:47:45 +01:00
|
|
|
ensure(!_cpu->check_state());
|
2020-10-26 02:02:39 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2020-03-08 12:48:06 +01:00
|
|
|
static thread_local struct thread_cleanup_t
|
|
|
|
|
{
|
2020-11-12 21:24:35 +01:00
|
|
|
cpu_thread* _this = nullptr;
|
2020-03-09 17:18:39 +01:00
|
|
|
std::string name;
|
2020-03-08 12:48:06 +01:00
|
|
|
|
2020-03-09 17:18:39 +01:00
|
|
|
void cleanup()
|
2020-03-08 12:48:06 +01:00
|
|
|
{
|
2020-03-09 17:18:39 +01:00
|
|
|
if (_this == nullptr)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-08 12:48:06 +01:00
|
|
|
if (auto ptr = vm::g_tls_locked)
|
|
|
|
|
{
|
|
|
|
|
ptr->compare_and_swap(_this, nullptr);
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-06 00:43:14 +01:00
|
|
|
atomic_wait_engine::set_notify_callback(nullptr);
|
2020-10-26 02:02:39 +01:00
|
|
|
|
2020-10-29 03:09:21 +01:00
|
|
|
g_tls_log_control = [](const char*, u64){};
|
|
|
|
|
|
2020-11-19 07:12:01 +01:00
|
|
|
if (s_tls_thread_slot != umax)
|
|
|
|
|
{
|
|
|
|
|
cpu_counter::remove(_this);
|
|
|
|
|
}
|
2020-03-09 17:18:39 +01:00
|
|
|
|
2020-11-30 01:48:09 +01:00
|
|
|
s_cpu_lock.lock_unlock();
|
|
|
|
|
|
2020-11-19 07:12:01 +01:00
|
|
|
s_cpu_counter--;
|
2020-11-12 21:24:35 +01:00
|
|
|
|
2021-03-17 21:49:43 +01:00
|
|
|
g_tls_this_thread = nullptr;
|
2020-11-13 09:32:47 +01:00
|
|
|
|
2021-02-21 15:16:06 +01:00
|
|
|
g_threads_deleted++;
|
|
|
|
|
|
2020-03-09 17:18:39 +01:00
|
|
|
_this = nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
~thread_cleanup_t()
|
|
|
|
|
{
|
|
|
|
|
if (_this)
|
|
|
|
|
{
|
2021-01-15 19:28:45 +01:00
|
|
|
sys_log.warning("CPU Thread '%s' terminated abnormally!", name);
|
2020-03-09 17:18:39 +01:00
|
|
|
cleanup();
|
|
|
|
|
}
|
2020-03-08 12:48:06 +01:00
|
|
|
}
|
2020-11-12 21:24:35 +01:00
|
|
|
} cleanup;
|
|
|
|
|
|
|
|
|
|
cleanup._this = this;
|
|
|
|
|
cleanup.name = thread_ctrl::get_name();
|
2020-03-08 12:48:06 +01:00
|
|
|
|
2016-04-14 00:59:00 +02:00
|
|
|
// Check thread status
|
2021-01-22 09:11:54 +01:00
|
|
|
while (!(state & cpu_flag::exit) && thread_ctrl::state() != thread_state::aborting)
|
2015-11-26 09:06:29 +01:00
|
|
|
{
|
2017-02-05 15:06:03 +01:00
|
|
|
// Check stop status
|
2021-02-13 15:50:07 +01:00
|
|
|
const auto state0 = +state;
|
|
|
|
|
|
|
|
|
|
if (is_stopped(state0 - cpu_flag::stop))
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!(state0 & cpu_flag::stop))
|
2015-02-18 17:22:06 +01:00
|
|
|
{
|
2020-03-09 17:18:39 +01:00
|
|
|
cpu_task();
|
2020-07-30 11:07:18 +02:00
|
|
|
|
|
|
|
|
if (state & cpu_flag::ret && state.test_and_reset(cpu_flag::ret))
|
|
|
|
|
{
|
|
|
|
|
cpu_return();
|
|
|
|
|
}
|
|
|
|
|
|
2015-11-26 09:06:29 +01:00
|
|
|
continue;
|
2015-02-18 17:22:06 +01:00
|
|
|
}
|
2015-11-26 09:06:29 +01:00
|
|
|
|
2021-02-13 15:50:07 +01:00
|
|
|
thread_ctrl::wait_on(state, state0);
|
2020-07-30 11:07:18 +02:00
|
|
|
|
|
|
|
|
if (state & cpu_flag::ret && state.test_and_reset(cpu_flag::ret))
|
|
|
|
|
{
|
|
|
|
|
cpu_return();
|
|
|
|
|
}
|
2015-11-26 09:06:29 +01:00
|
|
|
}
|
2020-03-09 17:18:39 +01:00
|
|
|
|
|
|
|
|
// Complete cleanup gracefully
|
|
|
|
|
cleanup.cleanup();
|
2015-11-26 09:06:29 +01:00
|
|
|
}
|
|
|
|
|
|
2016-04-25 12:49:12 +02:00
|
|
|
cpu_thread::~cpu_thread()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-25 18:50:30 +01:00
|
|
|
cpu_thread::cpu_thread(u32 id)
|
|
|
|
|
: id(id)
|
2016-04-25 12:49:12 +02:00
|
|
|
{
|
2021-03-02 16:00:16 +01:00
|
|
|
while (Emu.GetStatus() == system_state::paused)
|
|
|
|
|
{
|
|
|
|
|
// Solve race between Emulator::Pause and this construction of thread which most likely is guarded by IDM mutex
|
|
|
|
|
state += cpu_flag::dbg_global_pause;
|
|
|
|
|
|
|
|
|
|
if (Emu.GetStatus() != system_state::paused)
|
|
|
|
|
{
|
|
|
|
|
// Emulator::Resume was called inbetween
|
|
|
|
|
state -= cpu_flag::dbg_global_pause;
|
|
|
|
|
|
|
|
|
|
// Recheck if state is inconsistent
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-03 05:05:21 +01:00
|
|
|
if (Emu.IsStopped())
|
|
|
|
|
{
|
|
|
|
|
// For similar race as above
|
|
|
|
|
state += cpu_flag::exit;
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-09 23:51:29 +01:00
|
|
|
g_threads_created++;
|
2016-04-25 12:49:12 +02:00
|
|
|
}
|
|
|
|
|
|
2021-02-13 15:50:07 +01:00
|
|
|
void cpu_thread::cpu_wait(bs_t<cpu_flag> old)
|
2021-01-22 09:11:54 +01:00
|
|
|
{
|
2021-02-13 15:50:07 +01:00
|
|
|
thread_ctrl::wait_on(state, old);
|
2021-01-22 09:11:54 +01:00
|
|
|
}
|
|
|
|
|
|
2019-06-06 20:32:35 +02:00
|
|
|
bool cpu_thread::check_state() noexcept
|
2015-07-01 00:25:52 +02:00
|
|
|
{
|
2017-02-06 19:36:46 +01:00
|
|
|
bool cpu_sleep_called = false;
|
2020-10-25 18:06:34 +01:00
|
|
|
bool cpu_can_stop = true;
|
2020-05-09 07:42:59 +02:00
|
|
|
bool escape, retval;
|
2017-02-06 19:36:46 +01:00
|
|
|
|
2015-02-18 17:22:06 +01:00
|
|
|
while (true)
|
2014-07-07 19:22:36 +02:00
|
|
|
{
|
2020-05-09 07:42:59 +02:00
|
|
|
// Process all flags in a single atomic op
|
2021-02-13 15:50:07 +01:00
|
|
|
bs_t<cpu_flag> state1;
|
2020-05-09 07:42:59 +02:00
|
|
|
const auto state0 = state.fetch_op([&](bs_t<cpu_flag>& flags)
|
2017-03-11 00:14:48 +01:00
|
|
|
{
|
2020-05-09 07:42:59 +02:00
|
|
|
bool store = false;
|
|
|
|
|
|
2020-11-30 01:48:09 +01:00
|
|
|
if (flags & cpu_flag::pause && s_tls_thread_slot != umax)
|
2020-10-09 19:33:12 +02:00
|
|
|
{
|
2020-10-29 17:29:35 +01:00
|
|
|
// Save value before state is saved and cpu_flag::wait is observed
|
2020-11-14 08:56:05 +01:00
|
|
|
if (s_tls_sctr == umax)
|
2020-11-09 20:53:32 +01:00
|
|
|
{
|
2020-11-14 08:56:05 +01:00
|
|
|
u64 ctr = g_suspend_counter;
|
|
|
|
|
|
|
|
|
|
if (flags & cpu_flag::wait)
|
|
|
|
|
{
|
|
|
|
|
if ((ctr & 3) == 2)
|
|
|
|
|
{
|
|
|
|
|
s_tls_sctr = ctr;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
s_tls_sctr = ctr;
|
|
|
|
|
}
|
2020-11-09 20:53:32 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2020-11-30 01:48:09 +01:00
|
|
|
// Cleanup after asynchronous remove()
|
|
|
|
|
if (flags & cpu_flag::pause && s_tls_thread_slot == umax)
|
|
|
|
|
{
|
|
|
|
|
flags -= cpu_flag::pause;
|
|
|
|
|
store = true;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-14 08:56:05 +01:00
|
|
|
s_tls_sctr = -1;
|
2020-10-09 19:33:12 +02:00
|
|
|
}
|
|
|
|
|
|
2020-10-26 18:14:16 +01:00
|
|
|
if (flags & cpu_flag::temp) [[unlikely]]
|
2020-10-25 18:06:34 +01:00
|
|
|
{
|
|
|
|
|
// Sticky flag, indicates check_state() is not allowed to return true
|
|
|
|
|
flags -= cpu_flag::temp;
|
2020-10-26 18:14:16 +01:00
|
|
|
flags -= cpu_flag::wait;
|
2020-10-25 18:06:34 +01:00
|
|
|
cpu_can_stop = false;
|
|
|
|
|
store = true;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-03 21:09:00 +01:00
|
|
|
if (cpu_can_stop && flags & cpu_flag::signal)
|
2017-03-11 00:14:48 +01:00
|
|
|
{
|
2020-05-09 07:42:59 +02:00
|
|
|
flags -= cpu_flag::signal;
|
|
|
|
|
cpu_sleep_called = false;
|
|
|
|
|
store = true;
|
2017-03-11 00:14:48 +01:00
|
|
|
}
|
2019-01-15 16:31:21 +01:00
|
|
|
|
2021-02-19 12:53:09 +01:00
|
|
|
// Can't process dbg_step if we only paused temporarily
|
|
|
|
|
if (cpu_can_stop && flags & cpu_flag::dbg_step)
|
|
|
|
|
{
|
|
|
|
|
if (u32 pc = get_pc(), *pc2 = get_pc2(); pc != umax && pc2)
|
|
|
|
|
{
|
|
|
|
|
if (pc != *pc2)
|
|
|
|
|
{
|
|
|
|
|
flags -= cpu_flag::dbg_step;
|
|
|
|
|
flags += cpu_flag::dbg_pause;
|
|
|
|
|
store = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Can't test, ignore flag
|
|
|
|
|
flags -= cpu_flag::dbg_step;
|
|
|
|
|
store = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-06 20:32:35 +02:00
|
|
|
// Atomically clean wait flag and escape
|
2021-02-13 15:50:07 +01:00
|
|
|
if (!(flags & (cpu_flag::exit + cpu_flag::ret + cpu_flag::stop)))
|
2019-06-06 20:32:35 +02:00
|
|
|
{
|
2021-05-28 10:56:06 +02:00
|
|
|
// Check pause flags which hold thread inside check_state (ignore suspend/debug flags on cpu_flag::temp)
|
|
|
|
|
if (flags & (cpu_flag::pause + cpu_flag::memory) || (cpu_can_stop && flags & (cpu_flag::dbg_global_pause + cpu_flag::dbg_pause + cpu_flag::suspend)))
|
2019-08-04 15:21:19 +02:00
|
|
|
{
|
2020-06-05 11:36:28 +02:00
|
|
|
if (!(flags & cpu_flag::wait))
|
|
|
|
|
{
|
|
|
|
|
flags += cpu_flag::wait;
|
|
|
|
|
store = true;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-09 07:42:59 +02:00
|
|
|
escape = false;
|
2021-02-13 15:50:07 +01:00
|
|
|
state1 = flags;
|
2020-05-09 07:42:59 +02:00
|
|
|
return store;
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-05 11:36:28 +02:00
|
|
|
if (flags & cpu_flag::wait)
|
2020-05-09 07:42:59 +02:00
|
|
|
{
|
|
|
|
|
flags -= cpu_flag::wait;
|
|
|
|
|
store = true;
|
2019-08-04 15:21:19 +02:00
|
|
|
}
|
|
|
|
|
|
2020-10-25 23:16:16 +01:00
|
|
|
retval = false;
|
2020-05-09 07:42:59 +02:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2020-10-26 18:14:16 +01:00
|
|
|
if (cpu_can_stop && !(flags & cpu_flag::wait))
|
2020-06-05 11:36:28 +02:00
|
|
|
{
|
|
|
|
|
flags += cpu_flag::wait;
|
|
|
|
|
store = true;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-25 23:16:16 +01:00
|
|
|
retval = cpu_can_stop;
|
2019-06-06 20:32:35 +02:00
|
|
|
}
|
|
|
|
|
|
2020-05-09 07:42:59 +02:00
|
|
|
escape = true;
|
2021-02-13 15:50:07 +01:00
|
|
|
state1 = flags;
|
2020-05-09 07:42:59 +02:00
|
|
|
return store;
|
|
|
|
|
}).first;
|
2019-09-21 14:31:01 +02:00
|
|
|
|
2019-06-06 20:32:35 +02:00
|
|
|
if (escape)
|
2015-08-10 21:39:52 +02:00
|
|
|
{
|
2020-11-19 07:12:01 +01:00
|
|
|
if (s_tls_thread_slot == umax && !retval)
|
2020-10-10 13:22:12 +02:00
|
|
|
{
|
|
|
|
|
// Restore thread in the suspend list
|
2020-11-19 07:12:01 +01:00
|
|
|
cpu_counter::add(this);
|
2020-10-10 13:22:12 +02:00
|
|
|
}
|
|
|
|
|
|
2021-09-17 11:01:29 +02:00
|
|
|
if ((state0 & (cpu_flag::pending + cpu_flag::temp)) == cpu_flag::pending)
|
2020-06-27 16:38:49 +02:00
|
|
|
{
|
|
|
|
|
// Execute pending work
|
|
|
|
|
cpu_work();
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-13 15:50:07 +01:00
|
|
|
if (retval)
|
|
|
|
|
{
|
|
|
|
|
cpu_on_stop();
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-09 08:47:45 +01:00
|
|
|
ensure(cpu_can_stop || !retval);
|
2020-05-09 07:42:59 +02:00
|
|
|
return retval;
|
2015-08-10 21:39:52 +02:00
|
|
|
}
|
2020-10-10 08:32:23 +02:00
|
|
|
|
2020-11-03 21:09:00 +01:00
|
|
|
if (cpu_can_stop && !cpu_sleep_called && state0 & cpu_flag::suspend)
|
2017-02-06 19:36:46 +01:00
|
|
|
{
|
|
|
|
|
cpu_sleep();
|
|
|
|
|
cpu_sleep_called = true;
|
2020-10-10 13:22:12 +02:00
|
|
|
|
2020-11-03 21:09:00 +01:00
|
|
|
if (s_tls_thread_slot != umax)
|
2020-10-10 13:22:12 +02:00
|
|
|
{
|
|
|
|
|
// Exclude inactive threads from the suspend list (optimization)
|
2020-11-19 07:12:01 +01:00
|
|
|
cpu_counter::remove(this);
|
2020-10-10 13:22:12 +02:00
|
|
|
}
|
|
|
|
|
|
2017-02-06 19:36:46 +01:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-03 21:09:00 +01:00
|
|
|
if (state0 & ((cpu_can_stop ? cpu_flag::suspend : cpu_flag::dbg_pause) + cpu_flag::dbg_global_pause + cpu_flag::dbg_pause))
|
2019-06-06 20:32:35 +02:00
|
|
|
{
|
2020-11-02 22:12:45 +01:00
|
|
|
if (state0 & cpu_flag::dbg_pause)
|
|
|
|
|
{
|
2021-03-02 12:59:19 +01:00
|
|
|
g_fxo->get<gdb_server>().pause_from(this);
|
2020-11-02 22:12:45 +01:00
|
|
|
}
|
|
|
|
|
|
2021-02-13 15:50:07 +01:00
|
|
|
cpu_wait(state1);
|
2019-06-06 20:32:35 +02:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2020-08-19 19:28:08 +02:00
|
|
|
if (state0 & cpu_flag::memory)
|
2020-06-05 11:36:28 +02:00
|
|
|
{
|
|
|
|
|
vm::passive_lock(*this);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-09 19:33:12 +02:00
|
|
|
// If only cpu_flag::pause was set, wait on suspend counter instead
|
|
|
|
|
if (state0 & cpu_flag::pause)
|
|
|
|
|
{
|
|
|
|
|
// Wait for current suspend_all operation
|
2020-10-29 17:29:35 +01:00
|
|
|
for (u64 i = 0;; i++)
|
2020-10-09 19:33:12 +02:00
|
|
|
{
|
2020-11-14 08:56:05 +01:00
|
|
|
u64 ctr = g_suspend_counter;
|
|
|
|
|
|
2021-02-01 16:24:08 +01:00
|
|
|
if (ctr >> 2 == s_tls_sctr >> 2 && state & cpu_flag::pause)
|
2020-10-29 17:29:35 +01:00
|
|
|
{
|
2020-11-18 07:17:15 +01:00
|
|
|
if (i < 20 || ctr & 1)
|
|
|
|
|
{
|
|
|
|
|
busy_wait(300);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2021-02-01 16:24:08 +01:00
|
|
|
// TODO: fix the workaround
|
|
|
|
|
g_suspend_counter.wait(ctr, -4, atomic_wait_timeout{100});
|
2020-11-18 07:17:15 +01:00
|
|
|
}
|
2020-10-29 17:29:35 +01:00
|
|
|
}
|
2020-11-14 08:56:05 +01:00
|
|
|
else
|
2020-10-29 17:29:35 +01:00
|
|
|
{
|
2020-11-14 08:56:05 +01:00
|
|
|
s_tls_sctr = -1;
|
2020-10-29 17:29:35 +01:00
|
|
|
break;
|
|
|
|
|
}
|
2020-10-09 19:33:12 +02:00
|
|
|
}
|
|
|
|
|
}
|
2019-06-06 20:32:35 +02:00
|
|
|
}
|
2013-11-03 20:23:16 +01:00
|
|
|
}
|
|
|
|
|
}
|
2016-07-27 23:43:22 +02:00
|
|
|
|
2018-10-11 00:17:19 +02:00
|
|
|
void cpu_thread::notify()
|
2017-02-22 11:10:55 +01:00
|
|
|
{
|
2021-06-27 10:43:48 +02:00
|
|
|
state.notify_one();
|
|
|
|
|
|
2019-09-27 18:59:40 +02:00
|
|
|
// Downcast to correct type
|
2018-10-11 00:17:19 +02:00
|
|
|
if (id_type() == 1)
|
2017-02-22 11:10:55 +01:00
|
|
|
{
|
2018-10-11 00:17:19 +02:00
|
|
|
thread_ctrl::notify(*static_cast<named_thread<ppu_thread>*>(this));
|
|
|
|
|
}
|
|
|
|
|
else if (id_type() == 2)
|
|
|
|
|
{
|
|
|
|
|
thread_ctrl::notify(*static_cast<named_thread<spu_thread>*>(this));
|
|
|
|
|
}
|
2021-01-22 09:11:54 +01:00
|
|
|
else if (id_type() != 0x55)
|
2018-10-11 00:17:19 +02:00
|
|
|
{
|
2020-12-09 16:04:52 +01:00
|
|
|
fmt::throw_exception("Invalid cpu_thread type");
|
2017-02-22 11:10:55 +01:00
|
|
|
}
|
2016-07-27 23:43:22 +02:00
|
|
|
}
|
2016-08-13 16:58:19 +02:00
|
|
|
|
2021-06-27 10:43:48 +02:00
|
|
|
cpu_thread& cpu_thread::operator=(thread_state)
|
|
|
|
|
{
|
|
|
|
|
state += cpu_flag::exit;
|
|
|
|
|
state.notify_one(cpu_flag::exit);
|
|
|
|
|
return *this;
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-28 08:43:37 +01:00
|
|
|
std::string cpu_thread::get_name() const
|
|
|
|
|
{
|
|
|
|
|
// Downcast to correct type
|
|
|
|
|
if (id_type() == 1)
|
|
|
|
|
{
|
|
|
|
|
return thread_ctrl::get_name(*static_cast<const named_thread<ppu_thread>*>(this));
|
|
|
|
|
}
|
2021-04-09 21:12:47 +02:00
|
|
|
|
|
|
|
|
if (id_type() == 2)
|
2020-02-28 08:43:37 +01:00
|
|
|
{
|
|
|
|
|
return thread_ctrl::get_name(*static_cast<const named_thread<spu_thread>*>(this));
|
|
|
|
|
}
|
2021-04-09 21:12:47 +02:00
|
|
|
|
|
|
|
|
fmt::throw_exception("Invalid cpu_thread type");
|
2019-09-08 22:59:08 +02:00
|
|
|
}
|
|
|
|
|
|
2020-11-23 16:58:59 +01:00
|
|
|
u32 cpu_thread::get_pc() const
|
|
|
|
|
{
|
|
|
|
|
const u32* pc = nullptr;
|
|
|
|
|
|
|
|
|
|
switch (id_type())
|
|
|
|
|
{
|
|
|
|
|
case 1:
|
|
|
|
|
{
|
|
|
|
|
pc = &static_cast<const ppu_thread*>(this)->cia;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case 2:
|
|
|
|
|
{
|
|
|
|
|
pc = &static_cast<const spu_thread*>(this)->pc;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2021-01-22 09:11:54 +01:00
|
|
|
case 0x55:
|
|
|
|
|
{
|
|
|
|
|
const auto ctrl = static_cast<const rsx::thread*>(this)->ctrl;
|
2021-05-22 20:46:10 +02:00
|
|
|
return ctrl ? ctrl->get.load() : umax;
|
2021-01-22 09:11:54 +01:00
|
|
|
}
|
2020-11-23 16:58:59 +01:00
|
|
|
default: break;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-22 20:46:10 +02:00
|
|
|
return pc ? atomic_storage<u32>::load(*pc) : u32{umax};
|
2020-11-23 16:58:59 +01:00
|
|
|
}
|
|
|
|
|
|
2021-02-19 12:53:09 +01:00
|
|
|
u32* cpu_thread::get_pc2()
|
|
|
|
|
{
|
|
|
|
|
switch (id_type())
|
|
|
|
|
{
|
|
|
|
|
case 1:
|
|
|
|
|
{
|
|
|
|
|
return &static_cast<ppu_thread*>(this)->dbg_step_pc;
|
|
|
|
|
}
|
|
|
|
|
case 2:
|
|
|
|
|
{
|
|
|
|
|
return &static_cast<spu_thread*>(this)->dbg_step_pc;
|
|
|
|
|
}
|
|
|
|
|
case 0x55:
|
|
|
|
|
{
|
|
|
|
|
const auto ctrl = static_cast<rsx::thread*>(this)->ctrl;
|
|
|
|
|
return ctrl ? &static_cast<rsx::thread*>(this)->dbg_step_pc : nullptr;
|
|
|
|
|
}
|
|
|
|
|
default: break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-25 16:19:02 +01:00
|
|
|
std::shared_ptr<CPUDisAsm> make_disasm(const cpu_thread* cpu);
|
|
|
|
|
|
2020-03-31 02:11:37 +02:00
|
|
|
std::string cpu_thread::dump_all() const
|
|
|
|
|
{
|
2021-01-22 09:11:54 +01:00
|
|
|
std::string ret = cpu_thread::dump_misc();
|
|
|
|
|
ret += '\n';
|
|
|
|
|
ret += dump_misc();
|
|
|
|
|
ret += '\n';
|
|
|
|
|
ret += dump_regs();
|
|
|
|
|
ret += '\n';
|
|
|
|
|
ret += dump_callstack();
|
2022-03-25 16:19:02 +01:00
|
|
|
ret += '\n';
|
|
|
|
|
|
|
|
|
|
if (u32 cur_pc = get_pc(); cur_pc != umax)
|
|
|
|
|
{
|
|
|
|
|
// Dump a snippet of currently executed code (may be unreliable with non-static-interpreter decoders)
|
|
|
|
|
auto disasm = make_disasm(this);
|
|
|
|
|
|
|
|
|
|
const auto rsx = try_get<rsx::thread>();
|
|
|
|
|
|
|
|
|
|
for (u32 i = (rsx ? rsx->try_get_pc_of_x_cmds_backwards(20, cur_pc).second : cur_pc - 4 * 20), count = 0; count < 30; count++)
|
|
|
|
|
{
|
|
|
|
|
u32 advance = disasm->disasm(i);
|
|
|
|
|
ret += disasm->last_opcode;
|
|
|
|
|
i += std::max(advance, 4u);
|
|
|
|
|
disasm->dump_pc = i;
|
|
|
|
|
ret += '\n';
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-01-22 09:11:54 +01:00
|
|
|
|
|
|
|
|
return ret;
|
2020-03-31 02:11:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string cpu_thread::dump_regs() const
|
|
|
|
|
{
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string cpu_thread::dump_callstack() const
|
|
|
|
|
{
|
2021-01-22 09:11:54 +01:00
|
|
|
std::string ret;
|
|
|
|
|
|
|
|
|
|
fmt::append(ret, "Call stack:\n=========\n0x%08x (0x0) called\n", get_pc());
|
|
|
|
|
|
|
|
|
|
for (const auto& sp : dump_callstack_list())
|
|
|
|
|
{
|
|
|
|
|
fmt::append(ret, "> from 0x%08x (sp=0x%08x)\n", sp.first, sp.second);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ret;
|
2020-03-31 02:11:37 +02:00
|
|
|
}
|
|
|
|
|
|
2020-07-03 06:56:55 +02:00
|
|
|
std::vector<std::pair<u32, u32>> cpu_thread::dump_callstack_list() const
|
2020-03-31 02:11:37 +02:00
|
|
|
{
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string cpu_thread::dump_misc() const
|
2016-08-13 16:58:19 +02:00
|
|
|
{
|
2021-01-27 20:46:30 +01:00
|
|
|
return fmt::format("Type: %s\n" "State: %s\n", id_type() == 1 ? "PPU" : id_type() == 2 ? "SPU" : "CPU", state.load());
|
2016-08-13 16:58:19 +02:00
|
|
|
}
|
2019-06-06 20:32:35 +02:00
|
|
|
|
2020-11-18 17:15:56 +01:00
|
|
|
bool cpu_thread::suspend_work::push(cpu_thread* _this) noexcept
|
2019-06-06 20:32:35 +02:00
|
|
|
{
|
2020-10-09 19:33:12 +02:00
|
|
|
// Can't allow pre-set wait bit (it'd be a problem)
|
2020-12-09 08:47:45 +01:00
|
|
|
ensure(!_this || !(_this->state & cpu_flag::wait));
|
2019-06-06 20:32:35 +02:00
|
|
|
|
2020-10-09 19:33:12 +02:00
|
|
|
do
|
2019-06-06 20:32:35 +02:00
|
|
|
{
|
2020-10-09 19:33:12 +02:00
|
|
|
// Load current head
|
2020-11-19 07:12:01 +01:00
|
|
|
next = s_pushed.load();
|
2020-10-09 19:33:12 +02:00
|
|
|
|
2020-10-17 13:55:31 +02:00
|
|
|
if (!next && cancel_if_not_suspended) [[unlikely]]
|
|
|
|
|
{
|
|
|
|
|
// Give up if not suspended
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-09 19:33:12 +02:00
|
|
|
if (!_this && next)
|
2020-04-29 14:40:41 +02:00
|
|
|
{
|
2020-10-09 19:33:12 +02:00
|
|
|
// If _this == nullptr, it only works if this is the first workload pushed
|
2020-11-19 07:12:01 +01:00
|
|
|
s_cpu_lock.lock_unlock();
|
2020-10-09 19:33:12 +02:00
|
|
|
continue;
|
2020-04-29 14:40:41 +02:00
|
|
|
}
|
2020-10-09 19:33:12 +02:00
|
|
|
}
|
2020-11-19 07:12:01 +01:00
|
|
|
while (!s_pushed.compare_and_swap_test(next, this));
|
2019-06-06 20:32:35 +02:00
|
|
|
|
2020-10-09 19:33:12 +02:00
|
|
|
if (!next)
|
2019-06-06 20:32:35 +02:00
|
|
|
{
|
2020-10-18 14:00:10 +02:00
|
|
|
// Monitor the performance only of the actual suspend processing owner
|
|
|
|
|
perf_meter<"SUSPEND"_u64> perf0;
|
|
|
|
|
|
2020-10-09 19:33:12 +02:00
|
|
|
// First thread to push the work to the workload list pauses all threads and processes it
|
2020-11-19 07:12:01 +01:00
|
|
|
std::lock_guard lock(s_cpu_lock);
|
|
|
|
|
|
2020-11-19 14:05:08 +01:00
|
|
|
u128 copy = s_cpu_bits.load();
|
2020-10-10 08:32:23 +02:00
|
|
|
|
2020-10-18 19:09:39 +02:00
|
|
|
// Try to prefetch cpu->state earlier
|
2020-11-19 07:12:01 +01:00
|
|
|
copy = cpu_counter::for_all_cpu(copy, [&](cpu_thread* cpu)
|
2020-10-18 19:09:39 +02:00
|
|
|
{
|
|
|
|
|
if (cpu != _this)
|
|
|
|
|
{
|
2020-11-24 06:18:31 +01:00
|
|
|
utils::prefetch_write(&cpu->state);
|
2020-11-19 07:12:01 +01:00
|
|
|
return true;
|
2020-10-18 19:09:39 +02:00
|
|
|
}
|
2020-11-19 07:12:01 +01:00
|
|
|
|
|
|
|
|
return false;
|
2020-10-18 19:09:39 +02:00
|
|
|
});
|
|
|
|
|
|
2020-11-14 08:56:05 +01:00
|
|
|
// Initialization (first increment)
|
|
|
|
|
g_suspend_counter += 2;
|
|
|
|
|
|
2020-11-19 07:12:01 +01:00
|
|
|
// Copy snapshot for finalization
|
2020-11-19 14:05:08 +01:00
|
|
|
u128 copy2 = copy;
|
2020-10-10 08:32:23 +02:00
|
|
|
|
2021-03-05 20:05:37 +01:00
|
|
|
copy = cpu_counter::for_all_cpu(copy, [&](cpu_thread* cpu, u32 /*index*/)
|
2020-10-10 08:32:23 +02:00
|
|
|
{
|
2020-11-19 07:12:01 +01:00
|
|
|
if (cpu->state.fetch_add(cpu_flag::pause) & cpu_flag::wait)
|
2020-10-10 08:32:23 +02:00
|
|
|
{
|
|
|
|
|
// Clear bits as long as wait flag is set
|
2020-11-19 07:12:01 +01:00
|
|
|
return false;
|
2020-10-10 08:32:23 +02:00
|
|
|
}
|
|
|
|
|
|
2020-11-19 07:12:01 +01:00
|
|
|
return true;
|
2019-06-06 20:32:35 +02:00
|
|
|
});
|
|
|
|
|
|
2020-11-19 07:12:01 +01:00
|
|
|
while (copy)
|
2020-10-09 19:33:12 +02:00
|
|
|
{
|
2020-10-10 08:32:23 +02:00
|
|
|
// Check only CPUs which haven't acknowledged their waiting state yet
|
2021-03-05 20:05:37 +01:00
|
|
|
copy = cpu_counter::for_all_cpu(copy, [&](cpu_thread* cpu, u32 /*index*/)
|
2020-10-09 19:33:12 +02:00
|
|
|
{
|
2020-10-10 08:32:23 +02:00
|
|
|
if (cpu->state & cpu_flag::wait)
|
2020-10-09 19:33:12 +02:00
|
|
|
{
|
2020-11-19 07:12:01 +01:00
|
|
|
return false;
|
2020-10-09 19:33:12 +02:00
|
|
|
}
|
2020-11-19 07:12:01 +01:00
|
|
|
|
|
|
|
|
return true;
|
2020-10-09 19:33:12 +02:00
|
|
|
});
|
2020-10-20 21:00:15 +02:00
|
|
|
|
2020-11-19 07:12:01 +01:00
|
|
|
if (!copy)
|
2020-10-30 03:17:00 +01:00
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-21 15:12:05 +01:00
|
|
|
utils::pause();
|
2020-10-09 19:33:12 +02:00
|
|
|
}
|
|
|
|
|
|
2020-11-14 08:56:05 +01:00
|
|
|
// Second increment: all threads paused
|
2020-11-09 20:53:32 +01:00
|
|
|
g_suspend_counter++;
|
|
|
|
|
|
2020-10-09 19:33:12 +02:00
|
|
|
// Extract queue and reverse element order (FILO to FIFO) (TODO: maybe leave order as is?)
|
2020-11-19 07:12:01 +01:00
|
|
|
auto* head = s_pushed.exchange(nullptr);
|
2020-10-09 19:33:12 +02:00
|
|
|
|
2020-11-09 01:41:56 +01:00
|
|
|
u8 min_prio = head->prio;
|
|
|
|
|
u8 max_prio = head->prio;
|
2020-10-17 13:30:24 +02:00
|
|
|
|
2020-10-09 19:33:12 +02:00
|
|
|
if (auto* prev = head->next)
|
2019-06-06 20:32:35 +02:00
|
|
|
{
|
2020-10-09 19:33:12 +02:00
|
|
|
head->next = nullptr;
|
|
|
|
|
|
|
|
|
|
do
|
|
|
|
|
{
|
|
|
|
|
auto* pre2 = prev->next;
|
|
|
|
|
prev->next = head;
|
|
|
|
|
|
|
|
|
|
head = std::exchange(prev, pre2);
|
2020-10-17 13:30:24 +02:00
|
|
|
|
|
|
|
|
// Fill priority range
|
2020-11-09 01:41:56 +01:00
|
|
|
min_prio = std::min<u8>(min_prio, head->prio);
|
|
|
|
|
max_prio = std::max<u8>(max_prio, head->prio);
|
2020-10-09 19:33:12 +02:00
|
|
|
}
|
|
|
|
|
while (prev);
|
2019-06-06 20:32:35 +02:00
|
|
|
}
|
|
|
|
|
|
2020-10-30 03:17:00 +01:00
|
|
|
// Execute prefetch hint(s)
|
|
|
|
|
for (auto work = head; work; work = work->next)
|
|
|
|
|
{
|
|
|
|
|
for (u32 i = 0; i < work->prf_size; i++)
|
|
|
|
|
{
|
2020-11-24 06:18:31 +01:00
|
|
|
utils::prefetch_write(work->prf_list[0]);
|
2020-10-30 03:17:00 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-19 07:12:01 +01:00
|
|
|
cpu_counter::for_all_cpu(copy2, [&](cpu_thread* cpu)
|
2020-10-18 19:09:39 +02:00
|
|
|
{
|
2020-11-24 06:18:31 +01:00
|
|
|
utils::prefetch_write(&cpu->state);
|
2020-11-19 07:12:01 +01:00
|
|
|
return true;
|
2020-10-18 19:09:39 +02:00
|
|
|
});
|
|
|
|
|
|
2020-10-09 19:33:12 +02:00
|
|
|
// Execute all stored workload
|
2020-10-17 13:30:24 +02:00
|
|
|
for (s32 prio = max_prio; prio >= min_prio; prio--)
|
2020-10-09 19:33:12 +02:00
|
|
|
{
|
2020-10-17 13:30:24 +02:00
|
|
|
// ... according to priorities
|
|
|
|
|
for (auto work = head; work; work = work->next)
|
|
|
|
|
{
|
|
|
|
|
// Properly sorting single-linked list may require to optimize the loop
|
|
|
|
|
if (work->prio == prio)
|
|
|
|
|
{
|
|
|
|
|
work->exec(work->func_ptr, work->res_buf);
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-10-09 19:33:12 +02:00
|
|
|
}
|
|
|
|
|
|
2020-11-14 08:56:05 +01:00
|
|
|
// Finalization (last increment)
|
2020-12-09 08:47:45 +01:00
|
|
|
ensure(g_suspend_counter++ & 1);
|
2019-06-06 20:32:35 +02:00
|
|
|
|
2020-11-19 07:12:01 +01:00
|
|
|
cpu_counter::for_all_cpu(copy2, [&](cpu_thread* cpu)
|
2019-06-06 20:32:35 +02:00
|
|
|
{
|
2020-10-10 08:32:23 +02:00
|
|
|
cpu->state -= cpu_flag::pause;
|
2020-11-19 07:12:01 +01:00
|
|
|
return true;
|
2019-07-22 23:37:35 +02:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2020-10-09 19:33:12 +02:00
|
|
|
// Seems safe to set pause on self because wait flag hasn't been observed yet
|
2020-11-14 08:56:05 +01:00
|
|
|
s_tls_sctr = g_suspend_counter;
|
|
|
|
|
_this->state += cpu_flag::pause + cpu_flag::wait + cpu_flag::temp;
|
2020-10-09 19:33:12 +02:00
|
|
|
_this->check_state();
|
2020-11-14 08:56:05 +01:00
|
|
|
s_tls_sctr = -1;
|
2020-10-17 13:55:31 +02:00
|
|
|
return true;
|
2019-06-06 20:32:35 +02:00
|
|
|
}
|
2020-10-09 19:33:12 +02:00
|
|
|
|
|
|
|
|
g_suspend_counter.notify_all();
|
2020-10-17 13:55:31 +02:00
|
|
|
return true;
|
2019-06-06 20:32:35 +02:00
|
|
|
}
|
2019-08-20 18:07:03 +02:00
|
|
|
|
2021-06-05 21:15:15 +02:00
|
|
|
void cpu_thread::cleanup() noexcept
|
|
|
|
|
{
|
|
|
|
|
ensure(!s_cpu_counter);
|
2019-08-20 18:07:03 +02:00
|
|
|
|
2020-10-18 14:00:10 +02:00
|
|
|
sys_log.notice("All CPU threads have been stopped. [+: %u]", +g_threads_created);
|
2020-10-12 23:27:27 +02:00
|
|
|
|
|
|
|
|
g_threads_deleted -= g_threads_created.load();
|
|
|
|
|
g_threads_created = 0;
|
2019-08-20 18:07:03 +02:00
|
|
|
}
|
2019-10-14 19:41:31 +02:00
|
|
|
|
|
|
|
|
void cpu_thread::flush_profilers() noexcept
|
|
|
|
|
{
|
2021-02-03 19:14:31 +01:00
|
|
|
if (!g_fxo->is_init<cpu_profiler>())
|
2019-10-14 19:41:31 +02:00
|
|
|
{
|
2020-12-09 16:04:52 +01:00
|
|
|
profiler.fatal("cpu_thread::flush_profilers() has been called incorrectly.");
|
2019-10-14 19:41:31 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-09 21:12:47 +02:00
|
|
|
if (g_cfg.core.spu_prof)
|
2019-10-14 19:41:31 +02:00
|
|
|
{
|
2021-03-02 12:59:19 +01:00
|
|
|
g_fxo->get<cpu_profiler>().registered.push(0);
|
2019-10-14 19:41:31 +02:00
|
|
|
}
|
|
|
|
|
}
|
2021-03-30 17:31:46 +02:00
|
|
|
|
|
|
|
|
u32 CPUDisAsm::DisAsmBranchTarget(s32 /*imm*/)
|
|
|
|
|
{
|
|
|
|
|
// Unused
|
|
|
|
|
return 0;
|
|
|
|
|
}
|