CCCPaste

delay_precise

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