• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
llvm入門
 

llvm入門

on

  • 7,272 views

 

Statistics

Views

Total Views
7,272
Views on SlideShare
7,179
Embed Views
93

Actions

Likes
18
Downloads
40
Comments
0

3 Embeds 93

http://developer.cybozu.co.jp 61
https://twitter.com 30
http://us-w1.rockmelt.com 2

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel

llvm入門 llvm入門 Presentation Transcript

  • LLVM入門2013/3/30 光成滋生(@herumi)x86/x64最適化勉強会5(#x86opti)
  • 目次 目標  LLVMで簡単な関数を作ってCから呼び出す 足し算関数を作ろう 比較と条件分岐 メモリアクセス ループ carryつき整数加算 注意 : 私はLLVM歴2週間の初心者です つまり、私がLLVMに入門した話…2013/3/30 #x86opti 5 2 /19
  • LLVM プログラミング言語や実行環境に依存しない仮想機械 をターゲットにした最適化支援コンパイラ基盤全般  LLVMアセンブラで書かれたプログラムの実行、最適化、タ ーゲット環境への変換などの機能がある LLVMアセンブラ  SSA(Static Single Assignment)ベース 変数の再代入はできない  型安全 i32, float, doubleなどの型情報を持つ  レジスタは任意個  モジュール(翻訳単位に分かれたプログラム)を合成できる  http://llvm.org/docs/LangRef.html2013/3/30 #x86opti 5 3 /19
  • ツール clang –S –emit-llvm <C/C++ソース>.c  C/C++からLLVMアセンブラ(以下LLVMと略)を生成 llc <LLVMアセンブラ>.ll  LLVMアセンブラからターゲットCPUのアセンブラを生成  -marchオプションでターゲットCPUを指定 x86, arm, mips, sparc, etc. llc –versionでサポートターゲット一覧表示 llc –mattr=helpでより詳細な設定一覧表示 lli <LLVMアセンブラ>.ll  LLVMアセンブラを仮想マシン上で実行する 当然リンカや逆アセンブラ、最適化ツールなどもある2013/3/30 #x86opti 5 4 /19
  • 足し算 二つのuint32_t変数を足して返す関数を作る define(関数定義) define i32 @add1(i32 %x, i32 %y) { entry: 関数名:@なんとか %ret = add i32 %x, %y レジスタ名:%なんとか ret i32 %ret } i32(32bitレジスタ) 符号は特に無い(使う命令で決める) i1なら1bitのレジスタ(フラグ) i128なら128bitのレジスタ entry(ラベル)  とりあえず一つラベルがいる add(加算命令), ret(関数から返る命令)  各命令にも型情報が必要2013/3/30 #x86opti 5 5 /19
  • アセンブル(1/3) アセンブルして標準出力に出す llc add.ll –o – // コメント削除 add1: leal (%rdi,%rsi), %eax ret  Linuxの64bit環境ではrdiが第一引数, rsiが第二引数 C/C++の呼び出し規約にしたがって処理される lealで eax ← rdi + rsiを実行 LLVMのaddが単純にx64のaddになるわけではない  x86用に出力してみる llc add.ll –o – -march=x86 add1: movl 4(%esp), %eax ; 一つ目の引数 addl 8(%esp), %eax ; 二つ目の引数 ret2013/3/30 #x86opti 5 6 /19
  • アセンブル(2/3) Intel形式で出してみる llc add.ll –o – -march=x86 -x86-asm-syntax=intel add1: mov EAX, DWORD PTR [ESP + 4] add EAX, DWORD PTR [ESP + 8] ret arm用に出力 llc add.ll –o – -march=arm add1: add r0, r0, r1 mov pc, lr 二項演算としては他にsub, mul, udiv(符号なし), sdiv(符号あり), urem, srem, fadd(浮動小数)など2013/3/30 #x86opti 5 7 /19
  • 比較と分岐(1/3) 二つの値の大きい方 define i32 @my_max(i32 %x, i32 %y) {  比較命令はicmp entry: %r = icmp ugt i32 %x, %y  icmpの戻り値は br i1 %r, label %gt, label %else 1bitの変数 gt:  ugt → 符号なしgt ret i32 %x else: 他にeq, ne, sltなど ret i32 %y  brでラベルに飛ぶ } elseは予約語ではない  なんでもいい my_max: cmpl %esi, %edi jbe .LBB4_2 movl %edi, %eax ret .LBB4_2: movl %esi, %eax ret2013/3/30 #x86opti 5 8 /19
  • 比較と分岐(2/3) 絶対値の場合  0より小さいかを見るにはslt(signed less than)  y = sub 0, xで-xを作る nsw(no signed wrap)  制御の合流 define i32 @my_abs(i32 %x) { phi命令を使う entry: %cmp = icmp slt i32 %x, 0 br i1 %cmp, label %lt, label %else my_abs: lt: test edi, edi %neg = sub nsw i32 0, %x jns else br label %exit neg edi else: else: br label %exit mov eax, edi exit: ret %ret = phi i32 [%neg,%lt], [%x,%else] ret i32 %ret }2013/3/30 #x86opti 5 9 /19
  • 分岐(3/3) selectを使う  cmpにしたがって値を選択 define i32 @my_max3(i32 %x, i32 %y) { entry: %cmp = icmp ugt i32 %x, %y %cond = select i1 %cmp, i32 %x, i32 %y ret i32 %cond }  x86ではcmov cmp edi, esi cmova esi, edi ; edi > esiならesi ← edi mov eax, esi ret  cmovを使わせないとジャンプ命令が使われる -march=x86 –mattr=-cmov2013/3/30 #x86opti 5 10 /19
  • メモリアクセス(1/2) 次の関数を作ってみる void add(int *z, const int *x, const int *y) { *z = *x + *y; } loadとstore命令  alignを指定するとそのalignが仮定される  armでalign 1にするとバイト単位で読むコードに展開された x86/x64では気にしないw define void @add(i32* %z,i32* %x,i32* %y){ entry: %0 = load i32* %x, align 32 add: %1 = load i32* %y, align 32 mov eax,dword [rsi] %ret = add nsw i32 %0, %1 add eax,dword [rdx] store i32 %ret, i32* %z, align 32 mov dword [rdi],eax ret void ret }2013/3/30 #x86opti 5 11 /19
  • メモリアクセス(2/2) uint128_tの足し算を作ってみる  i128を使う そんなレジスタが無い環境(たいていの環境)でも使える  i64*をi128*にして値を読む 型変換にはbitcastを使う define void @add(i64* %z,i64* %x,i64* %y){ entry: %0 = bitcast i64* %x to i128* add: %1 = bitcast i64* %y to i128* mov rax, [rsi] %2 = load i128* %0, align 64 mov rcx, [rsi + 8] %3 = load i128* %1, align 64 add rax, [rdx] %4 = add i128 %2, %3 adc rcx, [rdx + 8] %5 = bitcast i64* %z to i128* mov [rdi + 8], rcx store i128 %4, i128* %5, align 64 mov [rdi], rax ret void ret }2013/3/30 #x86opti 5 12 /19
  • ループ uint64_tの配列の総和を求める  ループの更新では値の上書きができないのでphiを使う  getelementptr define i64 @sum(i64* %x,i64 %n) { entry: ポインタの計算に使う %n_is_0 = icmp eq i64 %n, 0  ループ変数が減る方向! br i1 %n_is_0, label %exit,label %lp lp: sum: %ip = phi i64 [0,%entry],[%i,%lp] xor eax, eax %retp = phi i64 [0,%entry],[%ret,%lp] test rsi, rsi %xi = getelementptr i64* %x, i64 %ip je exit %v = load i64* %xi lp: %ret = add i64 %retp, %v add rax, qword [rdi] %i = add i64 %ip, 1 add rdi, 8 %i_eq_n = icmp eq i64 %i, %n dec rsi br i1 %i_eq_n,label %exit,label %lp jne lp exit: exit: %r = phi i64 [0,%entry],[%ret,%lp] ret ret i64 %r }2013/3/30 #x86opti 5 13 /19
  • オーバーフロー(1/2) 多倍長演算のためにcarryを使う  組み込み関数llvm.uadd.with.overflow 使うにはdeclareが必要 戻り値は値とフラグのペア そこから値を取り出すにはextractvalueを使う // *z = x + y, return true if overflow // bool add_over(uint32_t *z, uint32_t x, uint32_t y); declare {i32, i1} @llvm.uadd.with.overflow.i32(i32, i32) define zeroext i1 @add_over(i32* %z, i32 %x, i32 %y) { entry: %0 = call {i32, i1} @llvm.uadd.with.overflow.i32(i32 %x,i32 %y) %ret = extractvalue {i32, i1} %0, 0 store i32 %ret, i32* %z %flag = extractvalue {i32, i1} %0, 1 ret i1 %flag }2013/3/30 #x86opti 5 14 /19
  • オーバーフロー(2/2) 前ページのコードの出力 // *z = x + y, return true if overflow // bool add_over(uint32_t *z, uint32_t x, uint32_t y); add_over: addl %edx, %esi movl %esi, (%rdi) setb %al ret ; al ← set 1 if overflow 小さい幅のレジスタから大きい幅のレジスタへの拡張  zext(符号なし)やsext(符号あり)を使う 困った  carryをaddに加える命令が無い! LLVMのソースコードを見ると内部的にはz=ADDE(x, y, carry) というのがあるようだが、それを呼べない…2013/3/30 #x86opti 5 15 /19
  • 多倍長整数加算の実装(1/3) 疑似コード addn(uint64_t *pz,const uint64_t *px,const uint64_t *py,size_t n){ bool CF = 0; for (size_t i = 0; i < n; i++) (pz[i],CF)=add_with_carry(px[i], py[i], CF); } add_with_carryは二つのレジスタとcarryを入力とし て加算の結果とCFのペアを返す define {i64, i1} @add_with_carry(i64 %x, i64 %y, i1 %c) { %vc1 = call {i64, i1}@llvm.uadd.with.overflow.i64(i64 %x,i64 %y) %v1 = extractvalue {i64, i1} %vc1, 0 %c1 = extractvalue {i64, i1} %vc1, 1 %zc = zext i1 %c to i64 %v2 = add i64 %v1, %zc %r1 = insertvalue {i64, i1} undef, i64 %v2, 0 %r2 = insertvalue {i64, i1} %r1, i1 %c1, 1 ret { i64, i1 } %r2 }2013/3/30 #x86opti 5 16 /19
  • 多倍長整数加算の実装(2/3) 作ったadd_with_carryを使って実装する  ループの一部 %x = load i64* %px_i, align 64 %y = load i64* %py_i, align 64 %rc1 = call {i64, i1} @add_with_carry(i64 %x, i64 %y, i1 %c_p) %r2 = extractvalue {i64, i1} %rc1, 0 %c = extractvalue {i64, i1} %rc1, 1  llc uint.ll –o – .lp: movq (%r15), %rsi movq (%r12), %rdi movzbl %dl, %edx callq add_with_carry ...  あれ、関数呼び出しのまま2013/3/30 #x86opti 5 17 /19
  • 多倍長整数加算の実装(3/3) optコマンドを使って最適化する  一度bc(ビットコード)に変換して逆アセンブルしてllcを適用 opt uint.ll -o - -std-compile-opts | llvm-dis –o - | llc –o - .lp: movq (%rsi), %r9 addq (%rdx), %r9 setb %al movzbl %r8b, %r8d andq $1, %r8 addq %r9, %r8 movq %r8, (%rdi)  関数が展開されて埋め込まれた すばらしい! 性能については後半に続く2013/3/30 #x86opti 5 18 /19
  • 1週間ほど触った雑感 よくできている  ドキュメントが充実している  コマンドエラーが親切  他のCPUの勉強がしやすい  最適化機能はかなり頑張ってる  gccのインラインアセンブラよりずっと使いやすい  プログラムコードがきれい 何をやってるのか追いかけやすい (私にとって)いまいちなところ  想像していたよりも抽象度が高い LLVMアセンブラと実行環境のアセンブラとの乖離 もちろん利点なのだが、うーん、それを隠蔽するかみたいな 異なるアーキテクチャのCPUを同じコードでやることのしわ寄せ2013/3/30 #x86opti 5 19 /19