FPGA開発日記

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

サイクル精度シミュレータSniperのメモリリーク解析備忘録

サイクル精度シミュレータSniper魔改造したら大量にメモリリークするようになってしまったので、それを修正した備忘録。

問題は自分の修正した部分。ベクトル命令は1命令が複数の命令に分解される可能性がある。

これを単純に実装しようとすると、命令キャッシュは同じアドレスの命令を再利用するため、同じアドレスを指定する命令は常に同じ動きをすることになる。

LMUL=8の場合、Spikeを改造して8つの命令が同じアドレスで異なるアドレスを指定しながら動くように変更していた。これが問題の発端だ。

これをSniper側で処理する場合、命令キャッシュに命令が残っていると再利用されてしまうので、命令キャッシュと命令デコードキャッシュをいったんクリアして再デコードしていた。

これが問題で、命令デコード情報自体はクリアし切れずにどんどんメモリ上に残っていき、シミュレーションが進むとメモリを大量に消費するようになっていた。

これを解決するためにはいろいろ考えたのだが、結局命令キャッシュと命令デコードキャッシュを同じアドレスで最大8つまで保持できるようにした。デコード情報を追加する条件は、

  • ベクトル命令である
  • 命令キャッシュ中にまだ命令が存在していない(LMUL=0)
  • 前に実行した命令と同じ命令アドレスをデコードした (LMUL>1)

とすればよく、やや強引ではあるが最大で8つの独立したベクトル命令がデコードできるようになる。

実はこれは本質的な解決策ではないと思っていて、やはりSpike側で同一アドレスの命令を8つに分割してトレースを取るのは良い方法とは言えず、やはり1つの命令でまとめて管理すべきだった。

そして、Sniper側の命令デコードによってMicroOpを最大8つ生成し、それぞれで命令実行を管理するべきだった。こっちのほうがスマートだ。

ただしこれを実装するのにはまた時間がかかってしまうので、とりあえずの修正方法として、命令キャッシュの独立上書きをサポートするようにした。

diff --git a/common/trace_frontend/trace_thread.h b/common/trace_frontend/trace_thread.h
index 767dd92..0417844 100644
--- a/common/trace_frontend/trace_thread.h
+++ b/common/trace_frontend/trace_thread.h
@@ -71,6 +71,11 @@ class TraceThread : public Runnable
       bool m_cleanup;
       bool m_started;

+  uint64_t m_vec_last_pc_addr;
+  uint8_t  m_vec_lmul_idx;
+  std::unordered_map<IntPtr, std::vector<Instruction *>> m_vec_icache;
+  std::unordered_map<IntPtr, std::vector<const dl::DecodedInst *>> m_vec_decoder_cache;
+
       void run();
       static Sift::Mode __handleInstructionCountFunc(void* arg, uint32_t icount)
       { return ((TraceThread*)arg)->handleInstructionCountFunc(icount); }
@@ -47,6 +48,8 @@ TraceThread::TraceThread(Thread *thread, SubsecondTime time_start, String tracef
    , m_blocked(false)
    , m_cleanup(cleanup)
    , m_started(false)
+   , m_vec_last_pc_addr(0)
+   , m_vec_lmul_idx(0)
    , m_stopped(false)
 {

@@ -377,13 +380,22 @@ SubsecondTime TraceThread::getCurrentTime() const

 Instruction* TraceThread::decode(Sift::Instruction &inst)
 {
-
-   if (m_decoder_cache.count(inst.sinst->addr) != 0) {
-     delete m_decoder_cache[inst.sinst->addr];
+   if (m_decoder_cache.count(inst.sinst->addr) == 0) {
+     m_decoder_cache[inst.sinst->addr] = staticDecode(inst);
+     m_vec_decoder_cache[inst.sinst->addr].push_back(m_decoder_cache[inst.sinst->addr]);
+   }
+   bool is_vector = m_decoder_cache[inst.sinst->addr]->is_vector();
+   if (is_vector) {
+     if (m_vec_decoder_cache[inst.sinst->addr].size() <= m_vec_lmul_idx) {
+       m_vec_decoder_cache[inst.sinst->addr].push_back(staticDecode(inst));
+     }
+   } else {
+     if (m_vec_decoder_cache.count(inst.sinst->addr) == 0) {
+       m_vec_decoder_cache[inst.sinst->addr].push_back(staticDecode(inst));
+     }
    }
-   m_decoder_cache[inst.sinst->addr] = staticDecode(inst);

-   const dl::DecodedInst& dec_inst = *(m_decoder_cache[inst.sinst->addr]);
+   const dl::DecodedInst &dec_inst = *(m_vec_decoder_cache[inst.sinst->addr][m_vec_lmul_idx]);

    OperandList list;

@@ -643,12 +655,38 @@ void TraceThread::handleInstructionDetailed(Sift::Instruction &inst, Sift::Instr

// Set up instruction
if (m_icache.count(inst.sinst->addr) == 0) {
  • m_vec_last_pc_addr = 0;
  • m_vec_lmul_idx = 0; m_icache[inst.sinst->addr] = decode(inst);
  • m_vec_icache[inst.sinst->addr].push_back(m_icache[inst.sinst->addr]);
  • m_vec_last_pc_addr = inst.sinst->addr;
  • } else {
  • Instruction *existed_inst = m_icache[inst.sinst->addr];
  • const std::vector *uops = existed_inst->getMicroOps();
  • const MicroOp uop = (uops)[0];
  • if (uop->isVector()) {
  • if (inst.sinst->addr == m_vec_last_pc_addr) {
    
  •   m_vec_lmul_idx++;
    
  • } else {
    
  •   m_vec_lmul_idx = 0;
    
  • }
    
  • if (m_vec_icache[inst.sinst->addr].size() <= m_vec_lmul_idx + 1) {
    
  •   m_vec_icache[inst.sinst->addr].push_back (decode(inst));
    
  • }
    
  • m_vec_last_pc_addr = inst.sinst->addr;
    
  • } else {
  • m_vec_last_pc_addr = 0;
    
  • m_vec_lmul_idx = 0;
    
  • } } + // 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]);
  • // const dl::DecodedInst &dec_inst = *(m_decoder_cache[inst.sinst->addr]);
  • const dl::DecodedInst &dec_inst = *(m_vec_decoder_cache[inst.sinst->addr][m_vec_lmul_idx]); +
  • Instruction *ins = m_vec_icache[inst.sinst->addr][m_vec_lmul_idx];
  • const MicroOp uop_t = (ins->getMicroOps())[0];

  • Instruction ins = m_icache[inst.sinst->addr]; DynamicInstruction dynins = prfmdl->createDynamicInstruction(ins, va2pa(inst.sinst->addr));

    // Add dynamic instruction info