//------------------------------------------------------------------------------------------------- /** * \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(); } }