//-------------------------------------------------------------------------------------------------
/**
* \brief delay the thread.
* \param target_pc The target performance counter at which wake up the thread.
*/
void delay_precise( uint_fast32_t target_pc );
//-------------------------------------------------------------------------------------------------
static void (*delay_precise_busywait)( uint64_t target_tsc );
static void set_func_ptr( uint64_t proc_info );
static void busywait_pause( uint64_t target_tsc );
static void busywait_tpause( uint64_t target_tsc );
static void busywait_mwaitx( uint64_t target_tsc );
static constexpr char zeros[CACHELINE_LEN] = {0};
//-------------------------------------------------------------------------------------------------
void delay_precise( uint64_t target_pc )
{
uint64_t cur_pc = SDL_GetPerformanceCounter();
const uint64_t target_tsc = (uint64_t)((double)(target_pc - cur_pc) * nr_tsc_in_pc);
// The number of nanoseconds in 160
constexpr uint64_t short_nap_len = 1000 * 160;
// Keep a record of max overshoot
uint64_t max_nap_len = short_nap_len;
while ( cur_pc + max_nap_len < target_pc ) {
hold_up_thread( short_nap_len );
const uint64_t new_pc = SDL_GetPerformanceCounter();
const uint64_t prev_nap_len = new_pc - cur_pc;
if ( prev_nap_len > max_nap_len ) {
max_nap_len = prev_nap_len;
}
cur_pc = new_pc;
}
if ( cur_pc < target_pc && (target_pc - cur_pc) > (max_nap_len - short_nap_len) ) {
const uint64_t mini_nap_len = (target_pc - cur_pc) - (max_nap_len - short_nap_len);
hold_up_thread( mini_nap_len );
cur_pc = SDL_GetPerformanceCounter();
}
while ( cur_pc + short_nap_len < target_pc ) {
hold_up_thread( short_nap_len );
cur_pc = SDL_GetPerformanceCounter();
}
delay_precise_busywait( target_tsc );
}
//-------------------------------------------------------------------------------------------------
static void set_func_ptr( uint64_t proc_info )
{
union ucl_x86_proc_info x86_proc_info;
x86_proc_info.whole = proc_info;
uint32_t eax, ebx, ecx, edx;
__cpuid( 0x7, eax, ebx, ecx, edx );
if ( ecx & 1<<5 ) { // Bit 05: WAITPKG
delay_precise_busywait = busywait_tpause;
return;
}
__cpuid( 0x80000001, eax, ebx, ecx, edx );
if ( ecx & 1<<29 ) { // Bit 29: MONITORX
__cpuid( 0x5, eax, ebx, ecx, edx );
if ( ecx & 1<<0 && ecx & 1<<1 ) { // Bit 00: "EMX(enumerate MONITOR/MWAIT extensions)",
// Bit 01: "IBE(interrupt break-event)"
if ( (ebx & 0xffff) != CACHELINE_LEN ) {
fprintf( stdout, "[Timer] Monitor line size is not equal to %d!\n", CACHELINE_LEN );
} else {
delay_precise_busywait = busywait_mwaitx;
return;
}
}
}
delay_precise_busywait = busywait_pause;
}
//-------------------------------------------------------------------------------------------------
static void busywait_pause( uint64_t target_tsc )
{
do {
_mm_pause();
} while ( _rdtsc() < target_tsc );
}
//-------------------------------------------------------------------------------------------------
__attribute__ ((target ("waitpkg")))
static void busywait_tpause( uint64_t target_tsc )
{
do {
_tpause( 0b1, target_tsc );
} while ( _rdtsc() < target_tsc );
}
//-------------------------------------------------------------------------------------------------
__attribute__ ((target ("mwaitx")))
static void busywait_mwaitx( uint64_t target_tsc )
{
uint64_t new_tsc = _rdtsc();
while ( new_tsc < target_tsc ) {
_mm_monitorx( (void*)zeros, 0, 0 );
_mm_mwaitx( 0b11, 0xf0, target_tsc - new_tsc );
new_tsc = _rdtsc();
}
}