FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

Sniperの動作原理をトレースする (1. 動作を追いかける)

Sniperの動作について、一度まとめてみることにした。シミュレーションをもとにトレースを作成してみる。

main() : standalone.cc
  - Simulatorをインスタンス化する
  - getTraceManager()->init()
   -getTraceManager()->run()
     - TraceThread::start()
     - TraceThread::run()
       - コアの実行モデルを取得
       - トレースが存在する限り実行
         - 命令キャッシュのWarmUp
         - handleInstructionDetailed()
           - 命令のデコード、命令キャッシュへの格納
           - addDetailedMemoryInfo()の実行により、メモリアクセス情報を取得する
           - Performance Modelに命令をPushする
           - Performance Modelのiterate()を実行する
             - handleInstruction()を呼び出す

TraceManagerはトレースの管理をする。m_threadsによりTraceThreadが格納されている?

      Monitor *m_monitor;
      std::vector<TraceThread *> m_threads;
      UInt32 m_num_threads_started;
      UInt32 m_num_threads_running;
      Semaphore m_done;

TraceManager()initrunについて。

void TraceManager::init()
{
   for (UInt32 i = 0 ; i < m_num_apps ; i++ )
   {
      newThread(i /*app_id*/, true /*first*/, false /*init_fifo*/, false /*spawn*/, SubsecondTime::Zero(), INVALID_THREAD_ID);
   }
}
void TraceManager::run()
{
   start();
   wait();
}

TraceManager::start()とは何なのか。

start()により、マネージャ内のすべてのスレッドが一斉にスタートする。

void TraceManager::start()
{
   // Begin of region-of-interest when running Sniper inside Sniper
   SimRoiStart();

   m_monitor->spawn();
   for(std::vector<TraceThread *>::iterator it = m_threads.begin(); it != m_threads.end(); ++it)
      (*it)->spawn();
}

spawn()により、スレッドが実行を開始する。

TraceThreadspawn()は、実際のスレッドを立ち上げて実行するものらしい。create(this)なので、TraceThreadrun()を実行することになるのと思われる。

void TraceThread::spawn()
{
   m__thread = _Thread::create(this);
   m__thread->run();
}

TraceThread::run()でやっていることは、まあよくわからん。

TraceThread::run()の動作概要

  1. トレースの作成を行う
   // Open the trace (be sure to do this before potentially blocking on reschedule() as this causes deadlock)
   m_trace.initStream();
   m_trace_has_pa = m_trace.getTraceHasPhysicalAddresses();
  1. スレッドがコアに割り当てられていない場合、スレッドのスケジューリングを行う?
   if (m_thread->getCore() == NULL)
   {
      // We didn't get scheduled on startup, wait here
      SubsecondTime time = SubsecondTime::Zero();
      m_thread->reschedule(time, NULL);
   }
  1. コアの実行モデルを取得する。パフォーマンスモデルは各CPUコアのモデルだと思われる。
   Core *core = m_thread->getCore();
   PerformanceModel *prfmdl = core->getPerformanceModel();
  1. トレースの存在する限り実行し続ける、ということ?
   while(have_first && m_trace.Read(next_inst))
   {
      if (m_blocked)
      {
         unblock();
      }

      core = m_thread->getCore();
      prfmdl = core->getPerformanceModel();

      bool do_icache_warmup = false;
      UInt64 icache_warmup_addr = 0, icache_warmup_size = 0;

      /* ... 命令実行 ... */
  1. 命令の実行のところ。BasicBlockが過去に実行したものでなければ、命令キャッシュをWarmUpする?
      if (m_bbv_end || m_bbv_last != inst.sinst->addr)
      {
         // We're the start of a new basic block
         core->countInstructions(m_bbv_base, m_bbv_count);
         // In cache-only mode, we'll want to do I-cache warmup
         if (m_bbv_base)
         {
            do_icache_warmup = true;
            icache_warmup_addr = m_bbv_base;
            icache_warmup_size = m_bbv_last - m_bbv_base;
         }
         // Set up new basic block info
         m_bbv_base = inst.sinst->addr;
         m_bbv_count = 0;
      }
      m_bbv_count++;
      m_bbv_last = inst.sinst->addr + inst.sinst->size;
      // Force BBV end on non-taken branches
      m_bbv_end = inst.is_branch;
  1. 命令の実行モード?サイクル測定の場合は、InstMode::DETAILEDhandleInstructionDetailed()を実行するものと思われる。
      switch(Sim()->getInstrumentationMode())
      {
         case InstMode::FAST_FORWARD:
            break;

         case InstMode::CACHE_ONLY:
            handleInstructionWarmup(inst, next_inst, core, do_icache_warmup, icache_warmup_addr, icache_warmup_size);
            break;

         case InstMode::DETAILED:
            handleInstructionDetailed(inst, next_inst, prfmdl);
            break;

         default:
            LOG_PRINT_ERROR("Unknown instrumentation mode");
      }

TraceThread::handleInstructionDetailed()の動作概要

  1. 命令のデコード。一度もデコードしたことがなければデコード結果を入れる。デコード結果を取得する。命令キャッシュから命令を取得し、DynamicInstructionを作成する。
   if (m_icache.count(inst.sinst->addr) == 0)
      m_icache[inst.sinst->addr] = decode(inst);
   // Here get the decoder instruction without checking, because we must have it for sure
   const dl::DecodedInst &dec_inst = *(m_decoder_cache[inst.sinst->addr]);

   Instruction *ins = m_icache[inst.sinst->addr];
   DynamicInstruction *dynins = prfmdl->createDynamicInstruction(ins, va2pa(inst.sinst->addr));
  1. 分岐命令でば、分岐命令情報を追加する。
   // Add dynamic instruction info
   if (inst.is_branch) {
      dynins->addBranch(inst.taken, va2pa(next_inst.sinst->addr), dec_inst.is_indirect_branch());
   }
  1. メモリアクセスオペランドの数だけ、addDetailedMemoryInfo()を実行する。この部分は、num_memory_operands()でメモリアクセスの回数を指定いたのだが、ベクトル命令の場合はオペランドで指定できないのでトレース側からメモリアクセス情報の回数を取得している。
   // Ignore memory-referencing operands in NOP instructions
   if (!dec_inst.is_nop())
   {
      const bool is_prefetch = dec_inst.is_prefetch();

      // for(uint32_t mem_idx = 0; mem_idx < Sim()->getDecoder()->num_memory_operands(&dec_inst); ++mem_idx)
      for(uint32_t mem_idx = 0; mem_idx < inst.num_addresses; ++mem_idx)
      {
         if (Sim()->getDecoder()->op_read_mem(&dec_inst, mem_idx))
         {
            addDetailedMemoryInfo(dynins, inst, dec_inst, mem_idx, Operand::READ, is_prefetch, prfmdl);
         }
      }

      for(uint32_t mem_idx = 0; mem_idx < inst.num_addresses; ++mem_idx)
      {
         if (Sim()->getDecoder()->op_write_mem(&dec_inst, mem_idx))
         {
            addDetailedMemoryInfo(dynins, inst, dec_inst, mem_idx, Operand::WRITE, is_prefetch, prfmdl);
         }
      }
   }
  1. パフォーマンスモデルに命令情報を追加し、パフォーマンスモデルを実行する
   // Push instruction
   prfmdl->queueInstruction(dynins);
   // simulate
   prfmdl->iterate();

TraceThread::addDetailedMemoryInfo()の概要

メモリアクセス情報を追加する。va2paによるアドレス変換に失敗すればno_mappingによりaddMemory(0)を実行する。アドレス変換に成功した場合はaddMemory(pa)を実行する。

   bool no_mapping = false;
   UInt64 pa = va2pa(mem_address, is_prefetch ? &no_mapping : NULL);

   if (no_mapping)
   {
      dynins->addMemory(
         inst.executed,
         SubsecondTime::Zero(),
         0,
         Sim()->getDecoder()->size_mem_op(&decoded_inst, mem_idx),
         op_type,
         0,
         HitWhere::PREFETCH_NO_MAPPING);
   }
   else
   {
      dynins->addMemory(
         inst.executed,
         SubsecondTime::Zero(),
         pa,
         Sim()->getDecoder()->size_mem_op(&decoded_inst, mem_idx),
         op_type,
         0,
         HitWhere::UNKNOWN);
   }

addMemory()では、メモリアクセス情報をひたすら格納している。

      static const UInt8 MAX_MEMORY = 32;
      MemoryInfo memory_info[MAX_MEMORY];

      void addMemory(bool e, SubsecondTime l, IntPtr a, UInt32 s, Operand::Direction dir, UInt32 num_misses, HitWhere::where_t hit_where)
      {
         LOG_ASSERT_ERROR(num_memory < MAX_MEMORY, "Got more than MAX_MEMORY(%d) memory operands", MAX_MEMORY);
         memory_info[num_memory].dir = dir;
         memory_info[num_memory].executed = e;
         memory_info[num_memory].latency = l;
         memory_info[num_memory].addr = a;
         memory_info[num_memory].size = s;
         memory_info[num_memory].num_misses = num_misses;
         memory_info[num_memory].hit_where = hit_where;
         num_memory++;
      }
``````
main() : standalone.cc
  - Simulatorをインスタンス化する
  - getTraceManager()->init()
   -getTraceManager()->run()
     - TraceThread::start()
     - TraceThread::run()
       - コアの実行モデルを取得
       - トレースが存在する限り実行
         - 命令キャッシュのWarmUp
         - handleInstructionDetailed()
           - 命令のデコード、命令キャッシュへの格納
           - addDetailedMemoryInfo()の実行により、メモリアクセス情報を取得する
           - Performance Modelに命令をPushする
           - Performance Modelのiterate()を実行する
             - handleInstruction()を呼び出す

Sniperの動作について、一度まとめてみることにした。シミュレーションをもとにトレースを作成してみる。

TraceManagerはトレースの管理をする。m_threadsによりTraceThreadが格納されている?

      Monitor *m_monitor;
      std::vector<TraceThread *> m_threads;
      UInt32 m_num_threads_started;
      UInt32 m_num_threads_running;
      Semaphore m_done;

TraceManager()initrunについて。

void TraceManager::init()
{
   for (UInt32 i = 0 ; i < m_num_apps ; i++ )
   {
      newThread(i /*app_id*/, true /*first*/, false /*init_fifo*/, false /*spawn*/, SubsecondTime::Zero(), INVALID_THREAD_ID);
   }
}
void TraceManager::run()
{
   start();
   wait();
}

TraceManager::start()とは何なのか。

start()により、マネージャ内のすべてのスレッドが一斉にスタートする。

void TraceManager::start()
{
   // Begin of region-of-interest when running Sniper inside Sniper
   SimRoiStart();

   m_monitor->spawn();
   for(std::vector<TraceThread *>::iterator it = m_threads.begin(); it != m_threads.end(); ++it)
      (*it)->spawn();
}

spawn()により、スレッドが実行を開始する。

TraceThreadspawn()は、実際のスレッドを立ち上げて実行するものらしい。create(this)なので、TraceThreadrun()を実行することになるのと思われる。

void TraceThread::spawn()
{
   m__thread = _Thread::create(this);
   m__thread->run();
}

TraceThread::run()でやっていることは、まあよくわからん。

TraceThread::run()の動作概要

  1. トレースの作成を行う
   // Open the trace (be sure to do this before potentially blocking on reschedule() as this causes deadlock)
   m_trace.initStream();
   m_trace_has_pa = m_trace.getTraceHasPhysicalAddresses();
  1. スレッドがコアに割り当てられていない場合、スレッドのスケジューリングを行う?
   if (m_thread->getCore() == NULL)
   {
      // We didn't get scheduled on startup, wait here
      SubsecondTime time = SubsecondTime::Zero();
      m_thread->reschedule(time, NULL);
   }
  1. コアの実行モデルを取得する。パフォーマンスモデルは各CPUコアのモデルだと思われる。
   Core *core = m_thread->getCore();
   PerformanceModel *prfmdl = core->getPerformanceModel();
  1. トレースの存在する限り実行し続ける、ということ?
   while(have_first && m_trace.Read(next_inst))
   {
      if (m_blocked)
      {
         unblock();
      }

      core = m_thread->getCore();
      prfmdl = core->getPerformanceModel();

      bool do_icache_warmup = false;
      UInt64 icache_warmup_addr = 0, icache_warmup_size = 0;

      /* ... 命令実行 ... */
  1. 命令の実行のところ。BasicBlockが過去に実行したものでなければ、命令キャッシュをWarmUpする?
      if (m_bbv_end || m_bbv_last != inst.sinst->addr)
      {
         // We're the start of a new basic block
         core->countInstructions(m_bbv_base, m_bbv_count);
         // In cache-only mode, we'll want to do I-cache warmup
         if (m_bbv_base)
         {
            do_icache_warmup = true;
            icache_warmup_addr = m_bbv_base;
            icache_warmup_size = m_bbv_last - m_bbv_base;
         }
         // Set up new basic block info
         m_bbv_base = inst.sinst->addr;
         m_bbv_count = 0;
      }
      m_bbv_count++;
      m_bbv_last = inst.sinst->addr + inst.sinst->size;
      // Force BBV end on non-taken branches
      m_bbv_end = inst.is_branch;
  1. 命令の実行モード?サイクル測定の場合は、InstMode::DETAILEDhandleInstructionDetailed()を実行するものと思われる。
      switch(Sim()->getInstrumentationMode())
      {
         case InstMode::FAST_FORWARD:
            break;

         case InstMode::CACHE_ONLY:
            handleInstructionWarmup(inst, next_inst, core, do_icache_warmup, icache_warmup_addr, icache_warmup_size);
            break;

         case InstMode::DETAILED:
            handleInstructionDetailed(inst, next_inst, prfmdl);
            break;

         default:
            LOG_PRINT_ERROR("Unknown instrumentation mode");
      }

TraceThread::handleInstructionDetailed()の動作概要

  1. 命令のデコード。一度もデコードしたことがなければデコード結果を入れる。デコード結果を取得する。命令キャッシュから命令を取得し、DynamicInstructionを作成する。
   if (m_icache.count(inst.sinst->addr) == 0)
      m_icache[inst.sinst->addr] = decode(inst);
   // Here get the decoder instruction without checking, because we must have it for sure
   const dl::DecodedInst &dec_inst = *(m_decoder_cache[inst.sinst->addr]);

   Instruction *ins = m_icache[inst.sinst->addr];
   DynamicInstruction *dynins = prfmdl->createDynamicInstruction(ins, va2pa(inst.sinst->addr));
  1. 分岐命令でば、分岐命令情報を追加する。
   // Add dynamic instruction info
   if (inst.is_branch) {
      dynins->addBranch(inst.taken, va2pa(next_inst.sinst->addr), dec_inst.is_indirect_branch());
   }
  1. メモリアクセスオペランドの数だけ、addDetailedMemoryInfo()を実行する。この部分は、num_memory_operands()でメモリアクセスの回数を指定いたのだが、ベクトル命令の場合はオペランドで指定できないのでトレース側からメモリアクセス情報の回数を取得している。
   // Ignore memory-referencing operands in NOP instructions
   if (!dec_inst.is_nop())
   {
      const bool is_prefetch = dec_inst.is_prefetch();

      // for(uint32_t mem_idx = 0; mem_idx < Sim()->getDecoder()->num_memory_operands(&dec_inst); ++mem_idx)
      for(uint32_t mem_idx = 0; mem_idx < inst.num_addresses; ++mem_idx)
      {
         if (Sim()->getDecoder()->op_read_mem(&dec_inst, mem_idx))
         {
            addDetailedMemoryInfo(dynins, inst, dec_inst, mem_idx, Operand::READ, is_prefetch, prfmdl);
         }
      }

      for(uint32_t mem_idx = 0; mem_idx < inst.num_addresses; ++mem_idx)
      {
         if (Sim()->getDecoder()->op_write_mem(&dec_inst, mem_idx))
         {
            addDetailedMemoryInfo(dynins, inst, dec_inst, mem_idx, Operand::WRITE, is_prefetch, prfmdl);
         }
      }
   }
  1. パフォーマンスモデルに命令情報を追加し、パフォーマンスモデルを実行する
   // Push instruction
   prfmdl->queueInstruction(dynins);
   // simulate
   prfmdl->iterate();

TraceThread::addDetailedMemoryInfo()の概要

メモリアクセス情報を追加する。va2paによるアドレス変換に失敗すればno_mappingによりaddMemory(0)を実行する。アドレス変換に成功した場合はaddMemory(pa)を実行する。

   bool no_mapping = false;
   UInt64 pa = va2pa(mem_address, is_prefetch ? &no_mapping : NULL);

   if (no_mapping)
   {
      dynins->addMemory(
         inst.executed,
         SubsecondTime::Zero(),
         0,
         Sim()->getDecoder()->size_mem_op(&decoded_inst, mem_idx),
         op_type,
         0,
         HitWhere::PREFETCH_NO_MAPPING);
   }
   else
   {
      dynins->addMemory(
         inst.executed,
         SubsecondTime::Zero(),
         pa,
         Sim()->getDecoder()->size_mem_op(&decoded_inst, mem_idx),
         op_type,
         0,
         HitWhere::UNKNOWN);
   }

addMemory()では、メモリアクセス情報をひたすら格納している。

      static const UInt8 MAX_MEMORY = 32;
      MemoryInfo memory_info[MAX_MEMORY];

      void addMemory(bool e, SubsecondTime l, IntPtr a, UInt32 s, Operand::Direction dir, UInt32 num_misses, HitWhere::where_t hit_where)
      {
         LOG_ASSERT_ERROR(num_memory < MAX_MEMORY, "Got more than MAX_MEMORY(%d) memory operands", MAX_MEMORY);
         memory_info[num_memory].dir = dir;
         memory_info[num_memory].executed = e;
         memory_info[num_memory].latency = l;
         memory_info[num_memory].addr = a;
         memory_info[num_memory].size = s;
         memory_info[num_memory].num_misses = num_misses;
         memory_info[num_memory].hit_where = hit_where;
         num_memory++;
      }