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"
2022-09-12 18:39:08 +02:00
# include "Emu/Memory/vm_reservation.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 ) ;
2023-05-22 17:02:00 +02:00
extern thread_local std : : string ( * g_tls_log_prefix ) ( ) ;
2020-10-29 03:09:21 +01:00
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 " ;
2022-07-04 15:02:17 +02:00
case cpu_flag : : again : return " a " ;
2017-02-09 23:51:29 +01:00
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 " ;
2021-10-15 01:26:51 +02:00
case cpu_flag : : pending_recheck : return " pend-re " ;
2022-09-13 15:08:55 +02:00
case cpu_flag : : notify : return " ntf " ;
2022-09-06 17:59:23 +02:00
case cpu_flag : : yield : return " y " ;
case cpu_flag : : preempt : return " PREEMPT " ;
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-10-19 15:31:42 +02:00
// Avoid printing replicas or when not much changed
u64 new_samples = 0 ;
static constexpr u64 min_print_samples = 500 ;
static constexpr u64 min_print_all_samples = min_print_samples * 20 ;
2019-10-14 19:41:31 +02:00
void reset ( )
{
freq . clear ( ) ;
samples = 0 ;
idle = 0 ;
2022-10-19 15:31:42 +02:00
new_samples = 0 ;
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 )
{
2022-10-19 15:31:42 +02:00
if ( new_samples < min_print_samples | | samples = = idle )
2022-05-03 17:37:48 +02:00
{
2022-10-19 15:31:42 +02:00
if ( cpu_flag : : exit - ptr - > state )
{
profiler . notice ( " Thread \" %s \" [0x%08x]: %u samples, %u new (%.4f%% idle): Not enough new samples have been collected since the last print. " , ptr - > get_name ( ) , ptr - > id , samples , new_samples , 100. * idle / samples ) ;
}
2022-05-03 17:37:48 +02:00
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 ) ;
2022-10-19 15:31:42 +02:00
profiler . notice ( " Thread \" %s \" [0x%08x]: %u samples, %u new (%.4f%% idle):%s " , ptr - > get_name ( ) , ptr - > id , samples , new_samples , 100. * idle / samples , results ) ;
2022-05-03 17:37:48 +02:00
2022-10-19 15:31:42 +02:00
new_samples = 0 ;
2022-05-03 17:37:48 +02:00
}
2022-10-19 15:31:42 +02:00
static void print_all ( std : : unordered_map < std : : shared_ptr < cpu_thread > , sample_info > & threads )
2022-05-03 17:37:48 +02:00
{
2022-10-19 15:31:42 +02:00
u64 new_samples = 0 ;
// Print all results and cleanup
for ( auto & [ ptr , info ] : threads )
{
new_samples + = info . new_samples ;
info . print ( ptr ) ;
}
2022-05-03 17:37:48 +02:00
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 )
{
2022-10-19 15:31:42 +02:00
// This function collects thread information regardless of 'new_samples' member state
2022-05-03 17:37:48 +02:00
for ( auto & [ name , count ] : info . freq )
{
freq [ name ] + = count ;
}
samples + = info . samples ;
idle + = info . idle ;
}
2022-10-19 15:31:42 +02:00
if ( samples = = idle )
2022-05-03 17:37:48 +02:00
{
2022-10-19 15:31:42 +02:00
return ;
2022-05-03 17:37:48 +02:00
}
2022-10-19 15:31:42 +02:00
if ( new_samples < min_print_all_samples & & thread_ctrl : : state ( ) ! = thread_state : : aborting )
2022-05-03 17:37:48 +02:00
{
2022-10-19 15:31:42 +02:00
profiler . notice ( " All Threads: %u samples, %u new (%.4f%% idle): Not enough new samples have been collected since the last print. " , samples , new_samples , 100. * idle / samples ) ;
2022-05-03 17:37:48 +02:00
return ;
}
2022-06-22 11:10:40 +02:00
2022-10-19 15:31:42 +02:00
for ( auto & [ name , count ] : freq )
{
chart . emplace ( count , name ) ;
}
2022-05-03 17:37:48 +02:00
const std : : string results = format ( chart , samples , idle , true ) ;
2022-10-19 15:31:42 +02:00
profiler . notice ( " All Threads: %u samples, %u new (%.4f%% idle):%s " , samples , new_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-10-19 16:45:56 +02:00
if ( auto state = + ptr - > state ; cpu_flag : : exit - 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-10-19 16:45:56 +02:00
if ( cpu_flag : : wait - state )
2019-10-14 19:41:31 +02:00
{
info . freq [ name ] + + ;
2022-10-19 15:31:42 +02:00
info . new_samples + + ;
2019-10-14 19:41:31 +02:00
// 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
{
2022-10-19 16:45:56 +02:00
if ( state & ( cpu_flag : : dbg_pause + cpu_flag : : dbg_global_pause ) )
{
// Idle state caused by emulation pause is not accounted for
continue ;
}
2019-10-14 19:41:31 +02:00
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
2022-10-19 15:31:42 +02:00
sample_info : : print_all ( threads ) ;
2019-10-14 19:41:31 +02:00
}
2022-10-19 16:45:56 +02:00
if ( Emu . IsPaused ( ) )
{
thread_ctrl : : wait_for ( 5000 ) ;
continue ;
}
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
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 ( ) ( )
{
2023-05-22 17:02:00 +02:00
const auto old_prefix = g_tls_log_prefix ;
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 ;
2023-05-22 17:02:00 +02:00
std : : string ( * log_prefix ) ( ) = nullptr ;
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
2023-05-22 17:02:00 +02:00
g_tls_log_prefix = log_prefix ;
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 ( ) ;
2023-05-22 17:02:00 +02:00
cleanup . log_prefix = old_prefix ;
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 ( ) ;
2022-09-06 17:59:23 +02:00
state + = cpu_flag : : wait ;
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
2022-09-20 10:47:05 +02:00
state . wait ( 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 + + ;
2021-10-15 01:26:51 +02:00
if ( u32 * pc2 = get_pc2 ( ) )
{
* pc2 = umax ;
}
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
{
2022-09-20 10:47:05 +02:00
state . wait ( old ) ;
2021-01-22 09:11:54 +01:00
}
2022-09-13 21:16:16 +02:00
static atomic_t < u32 > s_dummy_atomic = 0 ;
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 ;
2022-09-13 15:08:55 +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 ;
2022-09-14 10:47:25 +02:00
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 ;
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
2022-08-08 07:33:49 +02:00
if ( flags & cpu_flag : : notify )
{
flags - = cpu_flag : : notify ;
store = true ;
}
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
2022-07-04 15:02:17 +02:00
if ( ! is_stopped ( flags ) & & flags . none_of ( cpu_flag : : ret ) )
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)
2022-09-15 07:02:20 +02:00
if ( flags & ( cpu_flag : : pause + cpu_flag : : memory ) | | ( cpu_can_stop & & flags & ( cpu_flag : : dbg_global_pause + cpu_flag : : dbg_pause + cpu_flag : : suspend + cpu_flag : : yield + cpu_flag : : preempt ) ) )
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 ;
}
2022-09-15 07:02:20 +02:00
if ( flags & ( cpu_flag : : yield + cpu_flag : : preempt ) & & cpu_can_stop )
2022-09-06 17:59:23 +02:00
{
flags - = ( cpu_flag : : yield + cpu_flag : : preempt ) ;
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
{
2022-09-18 20:19:34 +02:00
if ( flags & cpu_flag : : wait )
2020-06-05 11:36:28 +02:00
{
2022-09-18 20:19:34 +02:00
flags - = cpu_flag : : wait ;
2020-06-05 11:36:28 +02:00
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
2022-09-15 07:02:20 +02:00
if ( state0 & cpu_flag : : preempt & & cpu_can_stop )
2022-09-06 17:59:23 +02:00
{
if ( cpu_flag : : wait - state0 )
{
2022-09-14 21:59:59 +02:00
if ( ! escape | | ! retval )
{
// Yield itself
state0 + = cpu_flag : : yield ;
escape = false ;
}
2022-09-06 17:59:23 +02:00
}
if ( const u128 bits = s_cpu_bits )
{
reader_lock lock ( s_cpu_lock ) ;
cpu_counter : : for_all_cpu ( bits & s_cpu_bits , [ ] ( cpu_thread * cpu )
{
if ( cpu - > state . none_of ( cpu_flag : : wait + cpu_flag : : yield ) )
{
cpu - > state + = cpu_flag : : yield ;
}
return true ;
} ) ;
}
}
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
}
2022-08-10 15:17:59 +02:00
if ( cpu_can_stop & & state0 & cpu_flag : : pending )
2020-06-27 16:38:49 +02:00
{
// Execute pending work
cpu_work ( ) ;
2021-10-15 01:26:51 +02:00
2022-08-10 15:17:59 +02:00
if ( ( state1 ^ state ) - cpu_flag : : pending )
2021-10-15 01:26:51 +02:00
{
// Work could have changed flags
2022-08-08 07:33:49 +02:00
// Reset internal flags as if check_state() has just been called
cpu_sleep_called = false ;
2021-10-15 01:26:51 +02:00
continue ;
}
2020-06-27 16:38:49 +02:00
}
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
}
2022-09-06 17:59:23 +02:00
continue ;
}
2022-09-15 07:02:20 +02:00
if ( state0 & cpu_flag : : yield & & cpu_flag : : wait - state0 & & cpu_can_stop )
2022-09-06 17:59:23 +02:00
{
2022-09-14 18:45:11 +02:00
if ( auto spu = try_get < spu_thread > ( ) )
{
if ( spu - > raddr & & spu - > rtime = = vm : : reservation_acquire ( spu - > raddr ) & & spu - > getllar_spin_count < 10 )
{
// Reservation operation is a critical section (but this may result in false positives)
continue ;
}
}
else if ( auto ppu = try_get < ppu_thread > ( ) )
{
if ( ppu - > raddr & & ppu - > rtime = = vm : : reservation_acquire ( ppu - > raddr ) )
{
// Same
continue ;
}
}
2022-09-06 17:59:23 +02:00
// Short sleep when yield flag is present alone (makes no sense when other methods which can stop thread execution have been done)
2022-09-13 21:16:16 +02:00
// Pass a mask of a single bit which is often unused to avoid notifications
s_dummy_atomic . wait ( 0 , 1u < < 30 , atomic_wait_timeout { 80'000 } ) ;
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 )
{
2022-08-25 11:12:29 +02:00
if ( state & cpu_flag : : exit )
{
// Must be notified elsewhere or self-raised
return * this ;
}
const auto old = state . fetch_add ( cpu_flag : : exit ) ;
if ( old & cpu_flag : : wait & & old . none_of ( cpu_flag : : again + cpu_flag : : exit ) )
{
state . notify_one ( cpu_flag : : exit ) ;
2022-09-12 18:39:08 +02:00
if ( auto thread = try_get < spu_thread > ( ) )
{
if ( u32 resv = atomic_storage < u32 > : : load ( thread - > raddr ) )
{
vm : : reservation_notifier ( resv ) . notify_one ( ) ;
}
}
2022-08-25 11:12:29 +02:00
}
2021-06-27 10:43:48 +02:00
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-07-25 17:57:47 +02:00
cpu_thread * cpu_thread : : get_next_cpu ( )
{
switch ( id_type ( ) )
{
case 1 :
{
return static_cast < ppu_thread * > ( this ) - > next_cpu ;
}
case 2 :
{
return static_cast < spu_thread * > ( this ) - > next_cpu ;
}
default : break ;
}
return nullptr ;
}
2022-03-25 16:19:02 +01:00
std : : shared_ptr < CPUDisAsm > make_disasm ( const cpu_thread * cpu ) ;
2022-06-22 11:00:06 +02:00
void cpu_thread : : dump_all ( std : : string & ret ) const
2020-03-31 02:11:37 +02:00
{
2022-06-22 11:00:06 +02:00
ret + = dump_misc ( ) ;
2021-01-22 09:11:54 +01:00
ret + = ' \n ' ;
2022-06-22 11:00:06 +02:00
dump_regs ( ret ) ;
2021-01-22 09:11:54 +01:00
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 ' ;
}
}
2020-03-31 02:11:37 +02:00
}
2022-06-22 11:00:06 +02:00
void cpu_thread : : dump_regs ( std : : string & ) const
2020-03-31 02:11:37 +02:00
{
}
std : : string cpu_thread : : dump_callstack ( ) const
{
2021-01-22 09:11:54 +01:00
std : : string ret ;
fmt : : append ( ret , " Call stack: \n ========= \n 0x%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
{
2022-06-22 11:10:40 +02:00
return fmt : : format ( " Type: %s; State: %s \n " , id_type ( ) = = 1 ? " PPU " : id_type ( ) = = 2 ? " SPU " : " RSX " , 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 ;
}
2022-07-14 21:07:02 +02:00
2022-10-03 10:29:09 +02:00
extern bool try_lock_spu_threads_in_a_state_compatible_with_savestates ( bool revert_lock )
2022-07-14 21:07:02 +02:00
{
const u64 start = get_system_time ( ) ;
// Attempt to lock for half a second, if somehow takes longer abort it
do
{
2022-10-03 10:29:09 +02:00
if ( revert_lock )
{
// Revert the operation of this function
break ;
}
2022-07-14 21:07:02 +02:00
if ( cpu_thread : : suspend_all ( nullptr , { } , [ ] ( )
{
return idm : : select < named_thread < spu_thread > > ( [ ] ( u32 , spu_thread & spu )
{
if ( ! spu . unsavable )
{
if ( Emu . IsPaused ( ) )
{
// If emulation is paused, we can only hope it's already in a state compatible with savestates
return ! ! ( spu . state & ( cpu_flag : : dbg_global_pause + cpu_flag : : dbg_pause ) ) ;
}
else
{
ensure ( ! spu . state . test_and_set ( cpu_flag : : dbg_global_pause ) ) ;
}
return false ;
}
return true ;
} ) . ret ;
} ) )
{
if ( Emu . IsPaused ( ) )
{
return false ;
}
// It's faster to lock once
reader_lock lock ( id_manager : : g_mutex ) ;
idm : : select < named_thread < spu_thread > > ( [ ] ( u32 , spu_thread & spu )
{
spu . state - = cpu_flag : : dbg_global_pause ;
} , idm : : unlocked ) ;
// For faster signalling, first remove state flags then batch notifications
idm : : select < named_thread < spu_thread > > ( [ ] ( u32 , spu_thread & spu )
{
if ( spu . state & cpu_flag : : wait )
{
spu . state . notify_one ( ) ;
}
} , idm : : unlocked ) ;
continue ;
}
return true ;
} while ( std : : this_thread : : yield ( ) , get_system_time ( ) - start < = 500'000 ) ;
idm : : select < named_thread < spu_thread > > ( [ & ] ( u32 , named_thread < spu_thread > & spu )
{
if ( spu . state . test_and_reset ( cpu_flag : : dbg_global_pause ) )
{
spu . state . notify_one ( ) ;
}
} ) ;
return false ;
}