何気に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
}
ここまでなら、なんとか自力でメモリを区別できそうなんだが、ローカルメモリ間でのメモリ転送とかあるよなぁと思ったんだけど、単純に展開してしまえば意外に答えが出そうな気がしてきた。
Tweet write: 2016/06/21/ 22:44:49