ひでみのアイデア帳

くだらないことなんだけど、忘れないために・・・

メインメモリとローカルメモリ

何気にSynverllのバージョンアップを進めてたり・・・

まぁ、コミケドリブンだからねぇ。

まずは一元化してたメモリを分散することを検討中。 Ver1.0はモジュール間のインターフェースをシンプルにすることだったのでVer2.0では複雑な方向に頑張ってみる。

メモリの展開で面倒なのがメインメモリ(DDR)なのかローカルメモリ(BlockRAM)なのかの判別である。

次のように1階層だけのソースコードで、global_bufferがメインメモリでlocal_bufferがローカルメモリだとする。

void buffer01(char *global_buffer){
  char local_buffer[1024];

  // ローカルバッファへデータ転送
  for( int i = 0; i < 1024; i++){
    local_buffer[i] = global_buffer[i];
  }

  // バッファ内のデータ入れ替え
  for( int i = 0; i < 1024; i++){
    local_buffer[ 1023 - i ] = local_buffer[i];
  }

  // ローカルバッファのデータを書き戻し
  for( int i = 0; i < 1024; i++){
    global_buffer[i] = local_buffer[i];
  }
}

LLVMすると、次のようになる。 メインメモリかローカルメモリかはallocaで判別することができる。

define void @buffer01(i8* nocapture %global_buffer) #0 {
.preheader3.preheader:
  %local_buffer = alloca [1024 x i8], align 1
  %local_buffer9 = getelementptr inbounds [1024 x i8], [1024 x i8]* %local_buffer, i32 0, i32 0
  %0 = getelementptr inbounds [1024 x i8], [1024 x i8]* %local_buffer, i32 0, i32 0
  call void @llvm.lifetime.start(i64 1024, i8* %0) #2
  call void @llvm.memcpy.p0i8.p0i8.i32(i8* %local_buffer9, i8* %global_buffer, i32 1024, i32 1, i1 false)
  br label %.preheader3

.preheader3:                                      ; preds = %.preheader3, %.preheader3.preheader
  %i1.05 = phi i32 [ %5, %.preheader3 ], [ 0, %.preheader3.preheader ]
  %1 = getelementptr inbounds [1024 x i8], [1024 x i8]* %local_buffer, i32 0, i32 %i1.05
  %2 = load i8, i8* %1, align 1, !tbaa !1
  %3 = sub nuw nsw i32 1023, %i1.05
  %4 = getelementptr inbounds [1024 x i8], [1024 x i8]* %local_buffer, i32 0, i32 %3
  store i8 %2, i8* %4, align 1, !tbaa !1
  %5 = add nuw nsw i32 %i1.05, 1
  %exitcond7 = icmp eq i32 %5, 1024
  br i1 %exitcond7, label %.preheader.preheader, label %.preheader3

.preheader.preheader:                             ; preds = %.preheader3
  call void @llvm.memcpy.p0i8.p0i8.i32(i8* %global_buffer, i8* nonnull %local_buffer9, i32 1024, i32 1, i1 false)
  call void @llvm.lifetime.end(i64 1024, i8* nonnull %0) #2
  ret void
}

次のソースコードの場合、実際にメモリを操作する上位階層(buffer02)でローカルメモリを確保して、下位関数(exec)で処理するような場合。 大抵のソースコードって、こっちのほうが多いだろう。

void exec(char *buffer)
{
  // バッファ内のデータ入れ替え
  for( int i = 0; i < 1024; i++){
    buffer[ 1023 - i ] = buffer[i];
  }
}

void buffer02(char *global_buffer){
  char local_buffer[1024];

  // ローカルバッファへデータ転送
  for( int i = 0; i < 1024; i++){
    local_buffer[i] = global_buffer[i];
  }

  // 実行
  exec(local_buffer);

  // ローカルバッファのデータを書き戻し
  for( int i = 0; i < 1024; i++){
    global_buffer[i] = local_buffer[i];
  }
}

LLVMするとexec関数は次のようになる。 exec関数内ではbufferがローカルメモリなのかメインメモリなのか判別つかない。

define void @exec(i8* nocapture %buffer) #0 {
  br label %1

; <label>:1                                       ; preds = %1, %0
  %i.01 = phi i32 [ 0, %0 ], [ %6, %1 ]
  %2 = getelementptr inbounds i8, i8* %buffer, i32 %i.01
  %3 = load i8, i8* %2, align 1, !tbaa !1
  %4 = sub nuw nsw i32 1023, %i.01
  %5 = getelementptr inbounds i8, i8* %buffer, i32 %4
  store i8 %3, i8* %5, align 1, !tbaa !1
  %6 = add nuw nsw i32 %i.01, 1
  %exitcond = icmp eq i32 %6, 1024
  br i1 %exitcond, label %7, label %1

; <label>:7                                       ; preds = %1
  ret void
}

次に上位階層のbuffer02を見てみるとallocaでローカルメモリというのが判断できる。 これを下位関数(exec)に伝えられれば良いのかな? 逆に言えば、下位関数(exec)から遡らなければいけないのか・・・

define void @buffer02(i8* nocapture %global_buffer) #0 {
  %local_buffer = alloca [1024 x i8], align 1
  %local_buffer4 = getelementptr inbounds [1024 x i8], [1024 x i8]* %local_buffer, i32 0, i32 0
  %1 = getelementptr inbounds [1024 x i8], [1024 x i8]* %local_buffer, i32 0, i32 0
  call void @llvm.lifetime.start(i64 1024, i8* %1) #3
  call void @llvm.memcpy.p0i8.p0i8.i32(i8* %local_buffer4, i8* %global_buffer, i32 1024, i32 1, i1 false)
  call void @exec(i8* %1) #3
  call void @llvm.memcpy.p0i8.p0i8.i32(i8* %global_buffer, i8* %local_buffer4, i32 1024, i32 1, i1 false)
  call void @llvm.lifetime.end(i64 1024, i8* %1) #3
  ret void
}

ここまでなら、なんとか自力でメモリを区別できそうなんだが、ローカルメモリ間でのメモリ転送とかあるよなぁと思ったんだけど、単純に展開してしまえば意外に答えが出そうな気がしてきた。