2009-10-17 18:21:30 +00:00
/* PCSX2 - PS2 Emulator for PCs
2010-05-03 14:08:02 +00:00
* Copyright (C) 2002-2010 PCSX2 Dev Team
2009-10-17 18:21:30 +00:00
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
2021-09-01 16:31:46 -04:00
#include "common/Threading.h"
2022-05-09 20:28:57 +10:00
#include "common/Assertions.h"
2022-05-05 23:01:14 +10:00
#ifdef _WIN32
#include "common/RedtapeWindows.h"
#endif
2009-10-17 18:21:30 +00:00
2022-05-09 20:28:57 +10:00
#include <limits>
2009-10-17 18:21:30 +00:00
// --------------------------------------------------------------------------------------
// Semaphore Implementations
// --------------------------------------------------------------------------------------
2022-03-25 05:12:52 -05:00
void Threading :: WorkSema :: WaitForWork ()
{
// State change:
// SLEEPING, SPINNING: This is the worker thread and it's clearly not asleep or spinning, so these states should be impossible
// RUNNING_0: Change state to SLEEPING, wake up thread if WAITING_EMPTY
// RUNNING_N: Change state to RUNNING_0 (and preserve WAITING_EMPTY flag)
s32 value = m_state . load ( std :: memory_order_relaxed );
2022-03-26 23:56:00 -05:00
pxAssert ( ! IsDead ( value ));
2022-03-25 05:12:52 -05:00
while ( ! m_state . compare_exchange_weak ( value , NextStateWaitForWork ( value ), std :: memory_order_acq_rel , std :: memory_order_relaxed ))
;
if ( IsReadyForSleep ( value ))
{
if ( value & STATE_FLAG_WAITING_EMPTY )
m_empty_sema . Post ();
m_sema . Wait ();
// Acknowledge any additional work added between wake up request and getting here
m_state . fetch_and ( STATE_FLAG_WAITING_EMPTY , std :: memory_order_acquire );
}
}
void Threading :: WorkSema :: WaitForWorkWithSpin ()
{
s32 value = m_state . load ( std :: memory_order_relaxed );
2022-03-26 23:56:00 -05:00
pxAssert ( ! IsDead ( value ));
2022-03-25 05:12:52 -05:00
while ( IsReadyForSleep ( value ))
{
if ( m_state . compare_exchange_weak ( value , STATE_SPINNING , std :: memory_order_release , std :: memory_order_relaxed ))
{
if ( value & STATE_FLAG_WAITING_EMPTY )
m_empty_sema . Post ();
value = STATE_SPINNING ;
break ;
}
}
u32 waited = 0 ;
while ( value < 0 )
{
if ( waited > SPIN_TIME_NS )
{
if ( ! m_state . compare_exchange_weak ( value , STATE_SLEEPING , std :: memory_order_relaxed ))
continue ;
m_sema . Wait ();
break ;
}
waited += ShortSpin ();
value = m_state . load ( std :: memory_order_relaxed );
}
// Clear back to STATE_RUNNING_0 (but preserve waiting empty flag)
m_state . fetch_and ( STATE_FLAG_WAITING_EMPTY , std :: memory_order_acquire );
}
2022-03-26 23:56:00 -05:00
bool Threading :: WorkSema :: WaitForEmpty ()
2022-03-25 05:12:52 -05:00
{
s32 value = m_state . load ( std :: memory_order_acquire );
while ( true )
{
if ( value < 0 )
2022-03-26 23:56:00 -05:00
return ! IsDead ( value ); // STATE_SLEEPING or STATE_SPINNING, queue is empty!
2022-04-16 00:28:37 -05:00
// Note: We technically only need memory_order_acquire on *failure* (because that's when we could leave without sleeping), but libstdc++ still asserts on failure < success
if ( m_state . compare_exchange_weak ( value , value | STATE_FLAG_WAITING_EMPTY , std :: memory_order_acquire ))
2022-03-25 05:12:52 -05:00
break ;
}
pxAssertDev ( ! ( value & STATE_FLAG_WAITING_EMPTY ), "Multiple threads attempted to wait for empty (not currently supported)" );
2022-05-05 23:01:12 +10:00
m_empty_sema . Wait ();
2022-03-26 23:56:00 -05:00
return ! IsDead ( m_state . load ( std :: memory_order_relaxed ));
2022-03-25 05:12:52 -05:00
}
2022-03-26 23:56:00 -05:00
bool Threading :: WorkSema :: WaitForEmptyWithSpin ()
2022-03-25 05:12:52 -05:00
{
s32 value = m_state . load ( std :: memory_order_acquire );
u32 waited = 0 ;
while ( true )
{
if ( value < 0 )
2022-03-26 23:56:00 -05:00
return ! IsDead ( value ); // STATE_SLEEPING or STATE_SPINNING, queue is empty!
2022-04-16 00:28:37 -05:00
if ( waited > SPIN_TIME_NS && m_state . compare_exchange_weak ( value , value | STATE_FLAG_WAITING_EMPTY , std :: memory_order_acquire ))
2022-03-25 05:12:52 -05:00
break ;
waited += ShortSpin ();
value = m_state . load ( std :: memory_order_acquire );
}
pxAssertDev ( ! ( value & STATE_FLAG_WAITING_EMPTY ), "Multiple threads attempted to wait for empty (not currently supported)" );
2022-05-05 23:01:12 +10:00
m_empty_sema . Wait ();
2022-03-26 23:56:00 -05:00
return ! IsDead ( m_state . load ( std :: memory_order_relaxed ));
}
void Threading :: WorkSema :: Kill ()
{
s32 value = m_state . exchange ( std :: numeric_limits < s32 >:: min (), std :: memory_order_release );
if ( value & STATE_FLAG_WAITING_EMPTY )
m_empty_sema . Post ();
}
void Threading :: WorkSema :: Reset ()
{
m_state = STATE_RUNNING_0 ;
2022-03-25 05:12:52 -05:00
}
#if !defined(__APPLE__) // macOS implementations are in DarwinSemaphore
Threading :: KernelSemaphore :: KernelSemaphore ()
{
#ifdef _WIN32
m_sema = CreateSemaphore ( nullptr , 0 , LONG_MAX , nullptr );
#else
sem_init ( & m_sema , false , 0 );
#endif
}
Threading :: KernelSemaphore ::~ KernelSemaphore ()
{
#ifdef _WIN32
CloseHandle ( m_sema );
#else
sem_destroy ( & m_sema );
#endif
}
void Threading :: KernelSemaphore :: Post ()
{
#ifdef _WIN32
ReleaseSemaphore ( m_sema , 1 , nullptr );
#else
sem_post ( & m_sema );
#endif
}
void Threading :: KernelSemaphore :: Wait ()
{
#ifdef _WIN32
2022-05-05 23:01:12 +10:00
WaitForSingleObject ( m_sema , INFINITE );
2022-03-25 05:12:52 -05:00
#else
sem_wait ( & m_sema );
#endif
}
2022-05-05 23:01:12 +10:00
bool Threading :: KernelSemaphore :: TryWait ()
2022-03-25 05:12:52 -05:00
{
#ifdef _WIN32
2022-05-05 23:01:12 +10:00
return WaitForSingleObject ( m_sema , 0 ) == WAIT_OBJECT_0 ;
2022-03-25 05:12:52 -05:00
#else
2022-05-05 23:01:12 +10:00
return sem_trywait ( & m_sema ) == 0 ;
2022-03-25 05:12:52 -05:00
#endif
}
2022-05-09 20:28:57 +10:00
#endif