mirror of https://github.com/PCSX2/pcsx2.git
aligned_stack branch progress!
* Implemented setjmp/longjmp method of escaping recompiled code, circumventing the deadly pitfalls of the g++ exception mess. * Use longjmp to jump to a "safe" location (outside recompiled code) for suspending and canceling the SysCoreThread (see StateThreadCheck_LongJmp) * I'm the stack daddy mack. That's right. Uh huh. Uh huh. * Test for m_detached in PersistentThread::IsSelf, since Linux likes to recycle thread handles in a hurry (like, as in, consecutively) * Fix a deadlock in the Consoole Log (seems to be GTK+ specific, and in fact I'm pretty sure wxApp->Yield() isn't thread safe and thinks it's recursive even when it's not) git-svn-id: http://pcsx2.googlecode.com/svn/branches/aligned_stack@2035 96395faa-99c1-11dd-bbfe-3dabce05a288
This commit is contained in:
parent
047726a60c
commit
e3a5229076
|
@ -38,7 +38,7 @@
|
|||
Console.Error( ex.what() ); \
|
||||
}
|
||||
|
||||
#ifdef __GNUC__
|
||||
#ifdef __GNUG__
|
||||
# define DESTRUCTOR_CATCHALL __DESTRUCTOR_CATCHALL( __PRETTY_FUNCTION__ )
|
||||
#else
|
||||
# define DESTRUCTOR_CATCHALL __DESTRUCTOR_CATCHALL( __FUNCTION__ )
|
||||
|
|
|
@ -214,7 +214,9 @@ void Threading::PersistentThread::Block()
|
|||
|
||||
bool Threading::PersistentThread::IsSelf() const
|
||||
{
|
||||
return pthread_self() == m_thread;
|
||||
// Detached threads may have their pthread handles recycled as newer threads, causing
|
||||
// false IsSelf reports.
|
||||
return !m_detached && (pthread_self() == m_thread);
|
||||
}
|
||||
|
||||
bool Threading::PersistentThread::IsRunning() const
|
||||
|
|
|
@ -42,3 +42,25 @@
|
|||
|
||||
extern void SetCPUState(u32 sseMXCSR, u32 sseVUMXCSR);
|
||||
extern u32 g_sseVUMXCSR, g_sseMXCSR;
|
||||
|
||||
// SEH - "Built in" Structed Exception Handling support.
|
||||
// This should be available on Windows, via Microsoft or Intel compilers (I'm pretty sure Intel
|
||||
// supports native SEH model). GUNC in Windows, or any compiler in a non-windows platform, will
|
||||
// ned to use setjmp/longjmp instead to exit recompiled code.
|
||||
//
|
||||
#if defined(_WIN32) && !defined(__GNUG__)
|
||||
# define PCSX2_SEH
|
||||
#endif
|
||||
|
||||
#ifndef PCSX2_SEH
|
||||
# include <setjmp.h>
|
||||
|
||||
enum
|
||||
{
|
||||
SetJmp_Dispatcher = 1,
|
||||
SetJmp_Exit,
|
||||
};
|
||||
|
||||
extern __threadlocal jmp_buf SetJmp_RecExecute;
|
||||
extern __threadlocal jmp_buf SetJmp_StateCheck;
|
||||
#endif
|
||||
|
|
|
@ -431,6 +431,8 @@ __forceinline void rcntUpdate_hScanline()
|
|||
}
|
||||
}
|
||||
|
||||
bool CoreCancelDamnit = false;
|
||||
|
||||
__forceinline void rcntUpdate_vSync()
|
||||
{
|
||||
s32 diff = (cpuRegs.cycle - vsyncCounter.sCycle);
|
||||
|
@ -439,17 +441,24 @@ __forceinline void rcntUpdate_vSync()
|
|||
if (vsyncCounter.Mode == MODE_VSYNC)
|
||||
{
|
||||
eeRecIsReset = false;
|
||||
|
||||
#ifndef PCSX2_SEH
|
||||
if( CoreCancelDamnit || SysCoreThread::Get().HasPendingStateChangeRequest() )
|
||||
{
|
||||
longjmp( SetJmp_StateCheck, 1 );
|
||||
}
|
||||
#else
|
||||
mtgsThread.RethrowException();
|
||||
SysCoreThread::Get().StateCheckInThread();
|
||||
#endif
|
||||
if( eeRecIsReset )
|
||||
{
|
||||
eeRecIsReset = false;
|
||||
cpuSetBranch();
|
||||
|
||||
// Hack! GCC is unwilling to let us throw exceptions here.
|
||||
// (Ones in Exception::*, anyways.) Work around it by skipping
|
||||
// it.
|
||||
#ifdef _MSC_VER
|
||||
#ifndef PCSX2_SEH
|
||||
longjmp( SetJmp_RecExecute, SetJmp_Dispatcher );
|
||||
#else
|
||||
throw Exception::ForceDispatcherReg();
|
||||
#endif
|
||||
}
|
||||
|
|
11
pcsx2/GS.h
11
pcsx2/GS.h
|
@ -102,6 +102,7 @@ protected:
|
|||
int m_CopyCommandTally;
|
||||
int m_CopyDataTally;
|
||||
volatile bool m_RingBufferIsBusy;
|
||||
volatile bool m_PluginOpened;
|
||||
|
||||
// Counts the number of vsync frames queued in the MTGS ringbuffer. This is used to
|
||||
// throttle the number of frames allowed to be rendered ahead of time for games that
|
||||
|
@ -122,8 +123,6 @@ public:
|
|||
mtgsThreadObject();
|
||||
virtual ~mtgsThreadObject() throw();
|
||||
|
||||
void OnStart();
|
||||
|
||||
// Waits for the GS to empty out the entire ring buffer contents.
|
||||
// Used primarily for plugin startup/shutdown.
|
||||
void WaitGS();
|
||||
|
@ -145,11 +144,15 @@ public:
|
|||
|
||||
protected:
|
||||
void OpenPlugin();
|
||||
void ClosePlugin();
|
||||
|
||||
void OnStart();
|
||||
void OnResumeReady();
|
||||
|
||||
void OnSuspendInThread();
|
||||
void OnPauseInThread() {}
|
||||
void OnResumeInThread( bool IsSuspended );
|
||||
|
||||
void OnResumeReady();
|
||||
void OnCleanupInThread();
|
||||
|
||||
// Saves MMX/XMM REGS, posts an event to the mtgsThread flag and releases a timeslice.
|
||||
// For use in surrounding loops that wait on the mtgs.
|
||||
|
|
|
@ -50,7 +50,8 @@ void SysPageFaultExceptionFilter( int signal, siginfo_t *info, void * )
|
|||
{
|
||||
// Bad mojo! Completely invalid address.
|
||||
// Instigate a crash or abort emulation or something.
|
||||
assert( false );
|
||||
wxTrap();
|
||||
return;
|
||||
}
|
||||
|
||||
mmap_ClearCpuBlock( offset & ~m_pagemask );
|
||||
|
|
|
@ -79,7 +79,6 @@ struct MTGS_BufferedData
|
|||
|
||||
static __aligned(32) MTGS_BufferedData RingBuffer;
|
||||
extern bool renderswitch;
|
||||
static volatile bool gsIsOpened = false;
|
||||
|
||||
|
||||
#ifdef RINGBUF_DEBUG_STACK
|
||||
|
@ -98,6 +97,7 @@ mtgsThreadObject::mtgsThreadObject() :
|
|||
, m_CopyCommandTally( 0 )
|
||||
, m_CopyDataTally( 0 )
|
||||
, m_RingBufferIsBusy( false )
|
||||
, m_PluginOpened( false )
|
||||
, m_QueuedFrames( 0 )
|
||||
, m_packet_size( 0 )
|
||||
, m_packet_ringpos( 0 )
|
||||
|
@ -111,7 +111,7 @@ mtgsThreadObject::mtgsThreadObject() :
|
|||
|
||||
void mtgsThreadObject::OnStart()
|
||||
{
|
||||
gsIsOpened = false;
|
||||
m_PluginOpened = false;
|
||||
|
||||
m_RingPos = 0;
|
||||
m_WritePos = 0;
|
||||
|
@ -187,14 +187,6 @@ struct PacketTagType
|
|||
u32 data[3];
|
||||
};
|
||||
|
||||
static void _clean_close_gs( void* obj )
|
||||
{
|
||||
if( !gsIsOpened ) return;
|
||||
gsIsOpened = false;
|
||||
if( g_plugins != NULL )
|
||||
g_plugins->m_info[PluginId_GS].CommonBindings.Close();
|
||||
}
|
||||
|
||||
static void dummyIrqCallback()
|
||||
{
|
||||
// dummy, because MTGS doesn't need this mess!
|
||||
|
@ -203,7 +195,7 @@ static void dummyIrqCallback()
|
|||
|
||||
void mtgsThreadObject::OpenPlugin()
|
||||
{
|
||||
if( gsIsOpened ) return;
|
||||
if( m_PluginOpened ) return;
|
||||
|
||||
memcpy_aligned( RingBuffer.Regs, PS2MEM_GS, sizeof(PS2MEM_GS) );
|
||||
GSsetBaseMem( RingBuffer.Regs );
|
||||
|
@ -225,7 +217,7 @@ void mtgsThreadObject::OpenPlugin()
|
|||
throw Exception::PluginOpenError( PluginId_GS );
|
||||
}
|
||||
|
||||
gsIsOpened = true;
|
||||
m_PluginOpened = true;
|
||||
m_sem_OpenDone.Post();
|
||||
|
||||
GSCSRr = 0x551B4000; // 0x55190000
|
||||
|
@ -238,7 +230,6 @@ void mtgsThreadObject::ExecuteTaskInThread()
|
|||
PacketTagType prevCmd;
|
||||
#endif
|
||||
|
||||
pthread_cleanup_push( _clean_close_gs, this );
|
||||
while( true )
|
||||
{
|
||||
m_sem_event.WaitRaw(); // ... because this does a cancel test itself..
|
||||
|
@ -409,18 +400,34 @@ void mtgsThreadObject::ExecuteTaskInThread()
|
|||
}
|
||||
m_RingBufferIsBusy = false;
|
||||
}
|
||||
pthread_cleanup_pop( true );
|
||||
}
|
||||
|
||||
void mtgsThreadObject::ClosePlugin()
|
||||
{
|
||||
if( !m_PluginOpened ) return;
|
||||
m_PluginOpened = false;
|
||||
if( g_plugins != NULL )
|
||||
g_plugins->m_info[PluginId_GS].CommonBindings.Close();
|
||||
}
|
||||
|
||||
void mtgsThreadObject::OnSuspendInThread()
|
||||
{
|
||||
_clean_close_gs( NULL );
|
||||
ClosePlugin();
|
||||
_parent::OnSuspendInThread();
|
||||
}
|
||||
|
||||
void mtgsThreadObject::OnResumeInThread( bool isSuspended )
|
||||
{
|
||||
if( isSuspended )
|
||||
OpenPlugin();
|
||||
|
||||
_parent::OnResumeInThread( isSuspended );
|
||||
}
|
||||
|
||||
void mtgsThreadObject::OnCleanupInThread()
|
||||
{
|
||||
ClosePlugin();
|
||||
_parent::OnCleanupInThread();
|
||||
}
|
||||
|
||||
// Waits for the GS to empty out the entire ring buffer contents.
|
||||
|
@ -783,7 +790,7 @@ void mtgsThreadObject::SendGameCRC( u32 crc )
|
|||
|
||||
void mtgsThreadObject::WaitForOpen()
|
||||
{
|
||||
if( gsIsOpened ) return;
|
||||
if( m_PluginOpened ) return;
|
||||
Resume();
|
||||
|
||||
// Two-phase timeout on MTGS opening, so that possible errors are handled
|
||||
|
|
|
@ -253,6 +253,9 @@ void SysThreadBase::OnCleanupInThread()
|
|||
m_RunningLock.Unlock();
|
||||
}
|
||||
|
||||
void SysThreadBase::OnSuspendInThread() {}
|
||||
void SysThreadBase::OnResumeInThread( bool isSuspended ) {}
|
||||
|
||||
void SysThreadBase::StateCheckInThread( bool isCancelable )
|
||||
{
|
||||
switch( m_ExecMode )
|
||||
|
@ -327,6 +330,13 @@ SysCoreThread::SysCoreThread() :
|
|||
|
||||
SysCoreThread::~SysCoreThread() throw()
|
||||
{
|
||||
SysCoreThread::Cancel();
|
||||
}
|
||||
|
||||
extern bool CoreCancelDamnit;
|
||||
void SysCoreThread::Cancel( bool isBlocking )
|
||||
{
|
||||
CoreCancelDamnit = true;
|
||||
_parent::Cancel();
|
||||
}
|
||||
|
||||
|
@ -334,6 +344,7 @@ void SysCoreThread::Start()
|
|||
{
|
||||
if( g_plugins == NULL ) return;
|
||||
g_plugins->Init();
|
||||
CoreCancelDamnit = false; // belongs in OnStart actually, but I'm tired :P
|
||||
_parent::Start();
|
||||
}
|
||||
|
||||
|
|
|
@ -93,6 +93,12 @@ public:
|
|||
return m_ExecMode > ExecMode_Closed;
|
||||
}
|
||||
|
||||
bool HasPendingStateChangeRequest()
|
||||
{
|
||||
ExecutionMode mode = m_ExecMode;
|
||||
return (mode == ExecMode_Closing) || (mode == ExecMode_Pausing);
|
||||
}
|
||||
|
||||
bool IsClosed() const { return !IsOpen(); }
|
||||
|
||||
ExecutionMode GetExecutionMode() const { return m_ExecMode; }
|
||||
|
@ -164,6 +170,7 @@ public:
|
|||
virtual void ApplySettings( const Pcsx2Config& src );
|
||||
virtual void OnResumeReady();
|
||||
virtual void Reset();
|
||||
virtual void Cancel( bool isBlocking=true );
|
||||
|
||||
bool HasValidState()
|
||||
{
|
||||
|
|
|
@ -500,6 +500,8 @@ void ConsoleLogFrame::OnSemaphoreWait( wxCommandEvent& event )
|
|||
m_semaphore.Post();
|
||||
}
|
||||
|
||||
static const wxTimeSpan high_volume_timeout( 0, 0, 0, 500 );
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Deadlock protection: High volume logs will over-tax our message pump and cause the
|
||||
// GUI to become inaccessible. The cool solution would be a threaded log window, but wx
|
||||
|
@ -515,13 +517,21 @@ void ConsoleLogFrame::CountMessage()
|
|||
{
|
||||
if( !wxThread::IsMain() )
|
||||
{
|
||||
// Append an event that'll post up our semaphore. It'll get run "in
|
||||
// order" which means when it posts all queued messages will have been
|
||||
// processed.
|
||||
// Append an event that'll post up our semaphore. It'll typically get run "in
|
||||
// order" which means when it posts all queued messages will have been processed.
|
||||
|
||||
// GTK+ / Timeout: We need a timeout on our semaphore to avoid deadlocking in GTK+,
|
||||
// because for some reason it can't friggen process messages from a wxYield()
|
||||
// (which is used from mutex and semaphore locks on the main thread to handle
|
||||
// messages from child threads, like this one!).
|
||||
|
||||
// Leaving it enabled on Windows as well for now since it's probably a "good idea" to avoid
|
||||
// deadlocking in some totally unforseeably random happenstance sircumstance, and I don't
|
||||
// think it'll have an impact on performance. --air
|
||||
|
||||
wxCommandEvent evt( wxEVT_SemaphoreWait );
|
||||
GetEventHandler()->AddPendingEvent( evt );
|
||||
m_semaphore.WaitRaw();
|
||||
m_semaphore.WaitRaw( high_volume_timeout );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -313,8 +313,6 @@ u32* recGetImm64(u32 hi, u32 lo)
|
|||
// R5900 Dispatchers
|
||||
// =====================================================================================================
|
||||
|
||||
extern "C" void recEventTest();
|
||||
|
||||
static u32 g_lastpc = 0;
|
||||
static u32 s_store_ebp, s_store_esp;
|
||||
|
||||
|
@ -330,6 +328,13 @@ static DynGenFunc* JITCompileInBlock = NULL;
|
|||
static DynGenFunc* EnterRecompiledCode = NULL;
|
||||
static DynGenFunc* ExitRecompiledCode = NULL;
|
||||
|
||||
static void recEventTest()
|
||||
{
|
||||
pxAssert( !g_globalXMMSaved && !g_globalMMXSaved );
|
||||
_cpuBranchTest_Shared();
|
||||
pxAssert( !g_globalXMMSaved && !g_globalMMXSaved );
|
||||
}
|
||||
|
||||
// parameters:
|
||||
// espORebp - 0 for ESP, or 1 for EBP.
|
||||
// regval - current value of the register at the time the fault was detected (predates the
|
||||
|
@ -443,9 +448,10 @@ static DynGenFunc* _DynGen_EnterRecompiledCode()
|
|||
xMOV( ptr[ebp-4], ebx );
|
||||
|
||||
// Simulate a CALL function by pushing the call address and EBP onto the stack.
|
||||
xMOV( ptr32[esp+0x10+12], 0xffeeff );
|
||||
xMOV( ptr32[esp+0x1c], 0xffeeff );
|
||||
uptr& imm = *(uptr*)(xGetPtr()-4);
|
||||
xMOV( ptr32[esp+0x10+8], ebp );
|
||||
xMOV( ptr32[esp+0x18], ebp );
|
||||
xLEA( ebp, ptr32[esp+0x18] );
|
||||
|
||||
xMOV( &s_store_esp, esp );
|
||||
xMOV( &s_store_ebp, ebp );
|
||||
|
@ -668,59 +674,72 @@ void recStep( void )
|
|||
{
|
||||
}
|
||||
|
||||
#ifndef PCSX2_SEH
|
||||
|
||||
extern "C" void recEventTest()
|
||||
// <--- setjmp/longjmp model <---
|
||||
|
||||
#include "GS.h"
|
||||
#include "System/SysThreads.h"
|
||||
|
||||
static void StateThreadCheck_LongJmp()
|
||||
{
|
||||
#ifdef PCSX2_DEVBUILD
|
||||
// dont' remove this check unless doing an official release
|
||||
if( g_globalXMMSaved || g_globalMMXSaved)
|
||||
{
|
||||
DevCon.Error("PCSX2 Foopah! Frozen regs have not been restored!!!");
|
||||
DevCon.Error("g_globalXMMSaved = %d,g_globalMMXSaved = %d", g_globalXMMSaved, g_globalMMXSaved);
|
||||
}
|
||||
assert( !g_globalXMMSaved && !g_globalMMXSaved);
|
||||
#endif
|
||||
setjmp( SetJmp_StateCheck );
|
||||
|
||||
// Perform counters, interrupts, and IOP updates:
|
||||
_cpuBranchTest_Shared();
|
||||
|
||||
#ifdef PCSX2_DEVBUILD
|
||||
assert( !g_globalXMMSaved && !g_globalMMXSaved);
|
||||
#endif
|
||||
mtgsThread.RethrowException();
|
||||
SysCoreThread::Get().StateCheckInThread();
|
||||
}
|
||||
|
||||
static void recExecute()
|
||||
{
|
||||
StateThreadCheck_LongJmp();
|
||||
|
||||
switch( setjmp( SetJmp_RecExecute ) )
|
||||
{
|
||||
case SetJmp_Exit: break;
|
||||
|
||||
case 0:
|
||||
case SetJmp_Dispatcher:
|
||||
|
||||
// Typically the Dispatcher is invoked from the EventTest code, which clears
|
||||
// the FreezeRegs flag, so always be sure to reset it here:
|
||||
g_EEFreezeRegs = true;
|
||||
|
||||
while( true )
|
||||
EnterRecompiledCode();
|
||||
break;
|
||||
}
|
||||
|
||||
g_EEFreezeRegs = false;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
// ---> SEH Model --->
|
||||
|
||||
static void recExecute()
|
||||
{
|
||||
// Implementation Notes:
|
||||
// This function enter an endless loop, which is only escapable via C++ exception handling.
|
||||
// The loop is needed because some things in the rec use "ret" as a shortcut to
|
||||
// invoking DispatcherReg. These things are code bits which are called infrequently,
|
||||
// such as dyna_block_discard and dyna_page_reset.
|
||||
// [TODO] fix this comment to explain various code entry/exit points, when I'm not so tired!
|
||||
|
||||
try
|
||||
{
|
||||
while( true )
|
||||
{
|
||||
// Note: make sure the FreezeRegs boolean is reset to true here, because
|
||||
// it might be set to false, depending on if the rec exits from the context of
|
||||
// an EventTest or not.
|
||||
|
||||
// Typically the Dispatcher is invoked from the EventTest code, which clears
|
||||
// the FreezeRegs flag, so always be sure to reset it here:
|
||||
g_EEFreezeRegs = true;
|
||||
|
||||
try {
|
||||
EnterRecompiledCode();
|
||||
}
|
||||
catch( Exception::ForceDispatcherReg& )
|
||||
{
|
||||
}
|
||||
catch( Exception::ForceDispatcherReg& ) { }
|
||||
}
|
||||
}
|
||||
catch( Exception::ExitRecExecute& )
|
||||
{
|
||||
}
|
||||
catch( Exception::ExitRecExecute& ) { }
|
||||
|
||||
g_EEFreezeRegs = false;
|
||||
}
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
void R5900::Dynarec::OpcodeImpl::recSYSCALL( void )
|
||||
|
@ -828,9 +847,19 @@ void recClear(u32 addr, u32 size)
|
|||
ClearRecLUT(PC_GETBLOCK(lowerextent), (upperextent - lowerextent) / 4);
|
||||
}
|
||||
|
||||
|
||||
#ifdef __GNUG__
|
||||
__threadlocal jmp_buf SetJmp_RecExecute;
|
||||
__threadlocal jmp_buf SetJmp_StateCheck;
|
||||
#endif
|
||||
|
||||
static void ExitRec()
|
||||
{
|
||||
#ifdef __GNUG__
|
||||
longjmp( SetJmp_RecExecute, SetJmp_Exit );
|
||||
#else
|
||||
throw Exception::ExitRecExecute();
|
||||
#endif
|
||||
}
|
||||
|
||||
// check for end of bios
|
||||
|
@ -1273,8 +1302,14 @@ void __fastcall dyna_block_discard(u32 start,u32 sz)
|
|||
DevCon.WriteLn("dyna_block_discard .. start=0x%08X size=%d", start, sz*4);
|
||||
recClear(start, sz);
|
||||
|
||||
// Note: this function is accessed via a JMP, and thus the RET here will exit
|
||||
// recompiled code and take us back to recExecute.
|
||||
// Stack trick: This function was invoked via a direct jmp, so manually pop the
|
||||
// EBP/stackframe before issuing a RET, else esp/ebp will be incorrect.
|
||||
|
||||
#ifdef _MSC_VER
|
||||
__asm leave; __asm jmp [ExitRecompiledCode]
|
||||
#else
|
||||
__asm__ __volatile__( "leave\n jmp *%[exitRec]\n" : : [exitRec] "m" (ExitRecompiledCode) : );
|
||||
#endif
|
||||
}
|
||||
|
||||
// called when a block under manual protection has been run enough times to be a
|
||||
|
@ -1285,10 +1320,11 @@ void __fastcall dyna_page_reset(u32 start,u32 sz)
|
|||
manual_counter[start >> 12]++;
|
||||
mmap_MarkCountedRamPage( start );
|
||||
|
||||
// Note: this function is accessed via a JMP, and thus the RET here will exit
|
||||
// recompiled code and take us back to recExecute.
|
||||
|
||||
__asm__ __volatile__( "leave\n jmp %[exitRec]\n" : : [exitRec] "m" (ExitRecompiledCode) : );
|
||||
#ifdef _MSC_VER
|
||||
__asm leave; __asm jmp [ExitRecompiledCode]
|
||||
#else
|
||||
__asm__ __volatile__( "leave\n jmp *%[exitRec]\n" : : [exitRec] "m" (ExitRecompiledCode) : );
|
||||
#endif
|
||||
}
|
||||
|
||||
void recRecompile( const u32 startpc )
|
||||
|
|
Loading…
Reference in New Issue