#pragma once #include "../Utilities/Thread.h" #include "../Utilities/bit_set.h" #include #include template concept DerivedFrom = std::is_base_of_v && std::is_convertible_v; // Thread state flags enum class cpu_flag : u32 { stop, // Thread not running (HLE, initial state) exit, // Irreversible exit wait, // Indicates waiting state, set by the thread itself temp, // Indicates that the thread cannot properly return after next check_state() pause, // Thread suspended by suspend_all technique suspend, // Thread suspended ret, // Callback return requested again, // Thread must complete the syscall after deserialization signal, // Thread received a signal (HLE) memory, // Thread must unlock memory mutex pending, // Thread has postponed work pending_recheck, // Thread needs to recheck if there is pending work before ::pending removal notify, // Flag meant solely to allow atomic notification on state without changing other flags yield, // Thread is being requested to yield its execution time if it's running preempt, // Thread is being requested to preempt the execution of all CPU threads dbg_global_pause, // Emulation paused dbg_pause, // Thread paused dbg_step, // Thread forced to pause after one step (one instruction, etc) __bitset_enum_max }; // Test stopped state constexpr bool is_stopped(bs_t state) { return !!(state & (cpu_flag::stop + cpu_flag::exit + cpu_flag::again)); } // Test paused state constexpr bool is_paused(bs_t state) { return !!(state & (cpu_flag::suspend + cpu_flag::dbg_global_pause + cpu_flag::dbg_pause)) && !is_stopped(state); } class cpu_thread { public: u64 block_hash = 0; protected: cpu_thread(u32 id); public: cpu_thread(const cpu_thread&) = delete; cpu_thread& operator=(const cpu_thread&) = delete; virtual ~cpu_thread(); void operator()(); // Self identifier const u32 id; // Public thread state atomic_bs_t state{cpu_flag::stop + cpu_flag::wait}; // Process thread state, return true if the checker must return bool check_state() noexcept; // Process thread state (pause) [[nodiscard]] bool test_stopped() { if (state) { if (check_state()) { return true; } } return false; } // Wrappers static constexpr bool is_stopped(bs_t s) { return ::is_stopped(s); } static constexpr bool is_paused(bs_t s) { return ::is_paused(s); } bool is_stopped() const { return ::is_stopped(state); } bool is_paused() const { return ::is_paused(state); } bool has_pause_flag() const { return !!(state & cpu_flag::pause); } // Check thread type u32 id_type() const { return id >> 24; } template T> T* try_get() { if constexpr (std::is_same_v, cpu_thread>) { return this; } else { if (id_type() == (T::id_base >> 24)) { return static_cast(this); } return nullptr; } } template T> const T* try_get() const { return const_cast(this)->try_get(); } u32 get_pc() const; u32* get_pc2(); // Last PC before stepping for the debugger (may be null) cpu_thread* get_next_cpu(); // Access next_cpu member if the is one void notify(); cpu_thread& operator=(thread_state); // Add/remove CPU state flags in an atomic operations, notifying if required void add_remove_flags(bs_t to_add, bs_t to_remove); // Thread stats for external observation static atomic_t g_threads_created, g_threads_deleted, g_suspend_counter; // Get thread name (as assigned to named_thread) std::string get_name() const; // Get CPU state dump (everything) virtual void dump_all(std::string&) const; // Get CPU register dump virtual void dump_regs(std::string& ret, std::any& custom_data) const; // Get CPU call stack dump virtual std::string dump_callstack() const; // Get CPU call stack list virtual std::vector> dump_callstack_list() const; // Get CPU dump of misc information virtual std::string dump_misc() const; // Thread entry point function virtual void cpu_task() = 0; // Callback for cpu_flag::suspend virtual void cpu_sleep() {} // Callback for cpu_flag::pending virtual void cpu_work() { state -= cpu_flag::pending + cpu_flag::pending_recheck; } // Callback for cpu_flag::ret virtual void cpu_return() {} // Callback for thread_ctrl::wait or RSX wait virtual void cpu_wait(bs_t old); // Callback for function abortion stats on Emu.Kill() virtual void cpu_on_stop() {} // For internal use struct suspend_work { // Task priority u8 prio; bool cancel_if_not_suspended; bool was_posted; // Size of prefetch list workload u32 prf_size; void* const* prf_list; void* func_ptr; void* res_buf; // Type-erased op executor void (*exec)(void* func, void* res); // Next object in the linked list suspend_work* next; // Internal method bool push(cpu_thread* _this) noexcept; }; // Suspend all threads and execute op (may be executed by other thread than caller!) template static auto suspend_all(cpu_thread* _this, std::initializer_list hints, F op) { constexpr u8 prio = Prio > 3 ? 3 : Prio; if constexpr (std::is_void_v>) { suspend_work work{prio, false, false, ::size32(hints), hints.begin(), &op, nullptr, [](void* func, void*) { std::invoke(*static_cast(func)); }}; work.push(_this); return; } else { std::invoke_result_t result; suspend_work work{prio, false, false, ::size32(hints), hints.begin(), &op, &result, [](void* func, void* res_buf) { *static_cast*>(res_buf) = std::invoke(*static_cast(func)); }}; work.push(_this); return result; } } template static suspend_work suspend_post(cpu_thread* /*_this*/, std::initializer_list hints, F& op) { constexpr u8 prio = Prio > 3 ? 3 : Prio; static_assert(std::is_void_v>, "cpu_thread::suspend_post only supports void as return type"); return suspend_work{prio, false, true, ::size32(hints), hints.begin(), &op, nullptr, [](void* func, void*) { std::invoke(*static_cast(func)); }}; } // Push the workload only if threads are being suspended by suspend_all() template static bool if_suspended(cpu_thread* _this, std::initializer_list hints, F op) { constexpr u8 prio = Prio > 3 ? 3 : Prio; static_assert(std::is_void_v>, "cpu_thread::if_suspended only supports void as return type"); { suspend_work work{prio, true, false, ::size32(hints), hints.begin(), &op, nullptr, [](void* func, void*) { std::invoke(*static_cast(func)); }}; return work.push(_this); } } // Cleanup thread counting information static void cleanup() noexcept; // Send signal to the profiler(s) to flush results static void flush_profilers() noexcept; template T = cpu_thread> static inline T* get_current() noexcept { if (const auto cpu = g_tls_this_thread) { return cpu->try_get(); } return nullptr; } private: static thread_local cpu_thread* g_tls_this_thread; }; template T = cpu_thread> inline T* get_current_cpu_thread() noexcept { return cpu_thread::get_current(); } class ppu_thread; class spu_thread;